mybatis SqlSession,SqlSessionFactory及spring SqlSessionTemplate

基本概念

  • SqlSession:
    • 數據庫CRUD及事務操作接口
    • 線程不安全,常用於Request範圍或method範圍
// Request範圍,4次sql執行共用一個SqlSession
sqlSessionManager.startManagedSession();
try {
    sqlSessionManager.query1();
    sqlSessionManager.query2();
    sqlSessionManager.update1();
    sqlSessionManager.update2();
    //...
}catch (Throwable t) {
    sqlSessionManager.rollback();
} finally {
    sqlSessionManager.close();
}
// 方法範圍,兩次調用創建了兩個session
SqlSessionManager.query1();
SqlSessionManager.query2();
  • SqlSessionFactory
    • 創建SqlSession的工廠類
  • SqlSessionManager/SqlSessionTemplate[裝飾器模式]
    • SqlSession裝飾器
    • 增強SqlSession生命週期管理
      這裏寫圖片描述

SqlSessionManager/SqlSessionTemplate源碼分析

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    // SqlSession動態代理,增強SqlSession生命週期管理
    private final SqlSession sqlSessionProxy;

    // 線程局部變量localSqlSession,每個線程內部都持有一個sqlSession副本,互不干擾
    private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        // 創建代理對象SqlSessionProxy
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionInterceptor());
    }

    private class SqlSessionInterceptor implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                // 如果線程內已存在sqlSession,直接共用,方法調用後由程序員手動關閉
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable t) {
                    throw ExceptionUtil.unwrapThrowable(t);
                }
            } else {
                // 1. 創建方法級sqlSession
                final SqlSession autoSqlSession = openSession();
                try {
                    // 2. 調用SqlSession方法執行sql
                    final Object result = method.invoke(autoSqlSession, args);
                    // 3.1 執行成功,提交事務
                    autoSqlSession.commit();
                    return result;
                } catch (Throwable t) {
                    // 3.2 執行異常,回滾事務
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(t);
                } finally {
                    // 4. 關閉SqlSession
                    autoSqlSession.close();
                }
            }
        }
    }
}
/**
 * 1. Thread safe, Spring managed, {@code SqlSession} that works with Spring
 * transaction management to ensure that that the actual SqlSession used is the
 * one associated with the current Spring transaction. 
 * spring管理,線程安全,確保SqlSession是當前spring事務的sqlSession
 * 2. SqlSession生命週期管理,包括commit, rollback, close
 * 3. 單例,所有DAO可以共用一個
 */
public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private final SqlSessionFactory sqlSessionFactory;

    private final ExecutorType executorType;

    private final SqlSession sqlSessionProxy;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
            PersistenceExceptionTranslator exceptionTranslator) {

        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        notNull(executorType, "Property 'executorType' is required");

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[] { SqlSession.class },
                new SqlSessionInterceptor());
    }

    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = getSqlSession(
                    SqlSessionTemplate.this.sqlSessionFactory,
                    SqlSessionTemplate.this.executorType,
                    SqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                // 非事務的sqlSession,執行完立即commit
                if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                if (sqlSession != null) {    
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
        }
    }
}

/**
   * 1. 非事務SqlSession,close
   * 2. 事務sqlsession, 更新引用計數器,當計數器爲0,即事務結束時,spring調用close callback
   */
  public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    ......
  }
  • Spring SqlSessionTemplate SqlSession生命週期管理
    • SqlSessionTemplate與SqlSessionManager實現SqlSession生命週期管理的方式相同
    • SqlSessionManager由使用者決定是否共用SqlSession
    • SqlSessionTemplate由Spring事務管理器決定是否共用SqlSession。
      • 事務內部共用SqlSession
      • 非事務不共用SqlSession

Spring創建SqlSessionFactory, SqlSessionTemplate, SqlSession流程

spring配置

<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>

<!-- scan for mappers and let them be autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="*.dao" />
</bean>

Spring生成MapperProxy bean流程圖
MapperFactoryBean生成MapperProxy bean時序圖

MapperProxy

  • DAO動態代理類:攔截DAO方法調用,調用SqlSession執行SQL,返回結果
  • Spring中採用SqlSessionTemplate執行SQL,對SqlSession的生命週期管理進行增強
// 攔截器
public class MapperProxy<T> implements InvocationHandler, Serializable {
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 執行sql,處理結果,並返回
    return mapperMethod.execute(sqlSession, args);
  }
  ......
}

// MapperProxyFactory newInstance()生成動態代理對象
Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, new MapperProxy<T>(sqlSession, mapperInterface, methodCache))

JAVA動態代理

  • 詳見代理模式(暫無)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章