MyBatis源碼解讀之SqlSession

1. 目的

經過源碼分析SqlSession功能實現、如何建立以及在Spring中是如何集成的。html

2. SqlSession 功能介紹

MyBatis工做的主要Java接口,經過這些接口你能夠執行命令,獲取mapper和管理事務
--代碼註釋git

查看大圖github

image

在圖中能夠看到,咱們操做數據庫的方法都在裏面。spring

3. SqlSession 具體功能實現

image

從類圖能夠看到SqlSession 有 DefaultSqlSession、SqlSessionManager2個實現類sql

  • DefaultSqlSession 是SqlSession的默認實現類,非線程安全
  • SqlSessionManager 爲線程安全的SqlSession實現,使用了ThreadLocal保存建立的SqlSession

3.1 DefaultSqlSession 源碼分析

/**
 *
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 * SqlSession 默認實現,非線程安全
 * 
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {


public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

/**
   * 返回單個查詢結果
   * @param statement 惟一標識匹配的語句.
   * @param parameter 查詢參數.
   * @param <T>
   * @return
   */
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      //期待返回一條記錄,但返回了多條
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
  
  /**
   * 返回集合結果
   * @param statement 惟一標識匹配的語句
   * @param parameter 查詢參數
   * @param rowBounds  返回結果的大小控制
   * @param <E>
   * @return
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 返回Map對象
   * @param statement 惟一標識匹配的語句.
   * @param parameter 查詢參數
   * @param mapKey key值,字段的屬性別名
   * @param rowBounds  返回結果的大小控制
   * @param <K>
   * @param <V>
   * @return
   */
  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
        configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<V>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }
  
  /**
   * 遊標查詢
   * @param <T>
   * @return
   */
  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * @param statement 惟一標識匹配的語句
   * @param parameter 查詢參數
   * @param rowBounds  返回結果的大小控制
   * @param handler 外部結果處理器
   */
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 增長
   * @return
   */
  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  /**
   * 修改
   * @return
   */
  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  /**
   * 增刪改公用方法
   * @param statement 惟一標識匹配的執行語句
   * @param parameter 參數
   * @return 返回影響的行數
   */
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 刪除
   * @return
   */
  @Override
  public int delete(String statement) {
    return update(statement, null);
  }
  
   /**
   * 提交
   * @param force forces connection commit
   */
  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 回滾
   * @param force forces connection rollback
   */
  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 提交批處理執行
   * @return 批處理提交更新記錄
   */
  @Override
  public List<BatchResult> flushStatements() {
    try {
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 關閉
   */
  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  /**
   * 獲取Mapper
   * @param type Mapper對應的Class類型
   * @param <T>
   * @return
   */
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
  
  // 省略其餘代碼
  
}

3.2 SqlSessionManager 源碼分析

先看類圖:數據庫

查看大圖apache

image

從圖中能夠看出 SqlSessionManager實現了SqlSessionFactory接口,又封裝了DefaultSqlSessionFactory
代碼以下:編程

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  //省略其餘代碼
  
  public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionManager(sqlSessionFactory);
  }
}

SqlSessionManager與DefaultSqlSessionFactory區別主要有2個:安全

  1. SqlSessionManager 在本地建立一個本地線程變量,ThreadLocal<SqlSession> localSqlSession,每當經過startManagedSession()獲取 SqlSession實例的時候,都會保存到SqlSession本地線程變量中。
public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }
 
 @Override
  public SqlSession openSession() {
    return sqlSessionFactory.openSession();
  }

在DefaultSqlSessionFactory中每次openSession都會產生一個新的DefaultSqlSessionsession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    try {
      //新建DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    } finally {
    }
  }

更詳細的源碼參考下節中:DefaultSqlSessionFactory 源碼分析

  1. SqlSessionManager實現了SqlSession接口,SqlSessionMananger集成了SqlSessionFactory 和 SqlSession的功能,經過SqlSessionManager,開發者能夠不在理會SqlSessionFacotry的存在,直接面向Session編程。

SqlSessionManager 內部提供了一個sqlSessionProxy,這個sqlSessionProxy提供了全部SqlSession接口的實現,而實現中正是使用了上面提到的本地線程保存的Sqlsession實例。

這樣,在同一個線程實現不一樣的sql操做,能夠複用本地線程Sqlsession,避免了DefaultSqlSessionFactory實現的每個sql操做都要建立新的Sqlsession實例。

讓咱們具體來看下sqlSessionProxy 的實現:

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    //建立SqlSession代理對象
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  
  
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    //使用代理對象執行數據庫操做
    return sqlSessionProxy.<T> selectOne(statement, parameter);
  }
  
  private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //從本地線程變量中獲取SqlSession實例
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        //不爲null
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        //爲null 則打開新鏈接
        final SqlSession autoSqlSession = openSession();
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }
  }
  
  //省略其餘代碼
  
}

4. SqlSession 如何建立

要了解SqlSession具體如何建立,咱們就須要知道SqlSessionFactory,也就是SqlSession工廠。

查看大圖

image

從類圖能夠看出 SqlSessionFactory 爲具體SqlSession工廠定義
DefaultSqlSessionFactory 實現了SqlSessionFactory,SqlSession是由DefaultSqlSessionFactory生成

4.1 SqlSessionFactory 接口定義

/**
* 經過外部傳入的connection 或 database 建立(打開) SqlSession
* 方法重載,經過參數不一樣建立SqlSession
*/
public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

4.2 DefaultSqlSessionFactory 源碼分析

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) { #1
    this.configuration = configuration;
  }

  //省略...

  /**
   * #mark 建立SqlSession
   * @param execType 執行器類型
   * @param level 事務隔離級別
   * @param autoCommit 是否自動提交
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { #2
    Transaction tx = null;
    try {
      //傳入的configuration獲取環境變量對象、Environment能夠配置多個環境配置
      final Environment environment = configuration.getEnvironment();
      //從環境對象中獲取事務工廠對象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //根據DataSource、事務隔離級別、自動提交建立事務對象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //#mark 新建執行者 20170820
      final Executor executor = configuration.newExecutor(tx, execType);
      //#mark 建立默認SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //異常狀況關閉事務
      closeTransaction(tx); // may have fetched a connection so lets call close() (可能已經獲取到數據庫鏈接,所以執行關閉)
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //重置錯誤上下文
      ErrorContext.instance().reset();
    }
  }

  //省略...

  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

  private void closeTransaction(Transaction tx) 
    if (tx != null) {
      try {
        tx.close();
      } catch (SQLException ignore) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

}

詳細說明:

  • 標註#1 經過構造方法傳入Configuration 配置對象,Configuration是一個貫穿全劇的對象

  • 標註#2 openSessionFromDataSource 顧名思義,從DataSource打開SqlSession,調用new DefaultSqlSession(configuration, executor, autoCommit) 構建SqlSession,具體實現查看源碼備註

Executor 、ErrorContext 後續詳細介紹

4.3 MyBatis如何執行SqlSession建立

從前面的描述中,咱們知道SqlSession由DefaultSqlSessionFactory 產生。經過IDEA關聯搜索功能,咱們找到了具體的調用類爲:SqlSessionFactoryBuilder。 SqlSessionFactoryBuilder 主要是獲取配置輸入流,建立DefaultSqlSessionFactory實例

先看下類圖:

image

SqlSessionFactoryBuilder 源碼分析

/**
 * Builds {@link SqlSession} instances.
 * SqlSession 工廠構造器
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  //省略
  
  /**
   * 經過字符流構建
   * @param reader 字符流
   * @param environment 環境變量
   * @param properties 屬性配置
   * @return
   */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) { #1
    try {
      //從字符流中建立XML配置對象
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //省略
  
  /**
   * 經過字節流構建
   * @param inputStream
   * @param environment
   * @param properties
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  
  /**
   * #mark SqlSessionFactory 初始化
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
  • 標註#1 經過字符流構建 SqlSessionFactory對象,全部的build方法都調用:new DefaultSqlSessionFactory(config) 構建

4.4 SqlSession 單元測試

依據測試規範,咱們找到測試類 SqlSessionTest,這個類方法比較多,我精簡出須要的部分。

/**
 * #mark 源碼學習入口
 */
public class SqlSessionTest extends BaseDataTest {
  private static SqlSessionFactory sqlMapper;

  @BeforeClass
  public static void setup() throws Exception {
    //初始化數據源,使用內存數據庫、運行一次自動銷燬
    createBlogDataSource();
    //資源文件地址
    final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
    //獲取資源文件字符流
    final Reader reader = Resources.getResourceAsReader(resource);
    //構建 SqlSessionFactory
    sqlMapper = new SqlSessionFactoryBuilder().build(reader);
  }


  /**
   * 測試SqlSession 開啓和關閉
   * @throws Exception
   */
  @Test
  public void shouldOpenAndClose() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.close();
  }

  /**
   * 測試提交一個未使用的SqlSession
   * @throws Exception
   */
  @Test
  public void shouldCommitAnUnUsedSqlSession() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.commit(true);
    session.close();
  }

  /**
   * 測試提交一個未使用的SqlSession
   * @throws Exception
   */
  @Test
  public void shouldRollbackAnUnUsedSqlSession() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    session.rollback(true);
    session.close();
  }

  /**
   * 跟蹤一個完整查詢
   * 查出全部做者 #20170831
   * @throws Exception
   */
  @Test
  public void shouldSelectAllAuthors() throws Exception {
    SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
    try {
      List<Author> authors = session.selectList("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAllAuthors");
      assertEquals(2, authors.size());
    } finally {
      session.close();
    }
  }
  
  //省略部分代碼

@BeforeClass
public static void setup() throws Exception {}
在這個方法中包含了具體的SqlSession的建立過程

5. SqlSession在Spring集成實現

5.1 SqlSessionFactoryBean 介紹

SqlSessionFactoryBean在基本的 MyBatis 中,session 工廠可使用 SqlSessionFactoryBuilder 來建立。而在 MyBatis-Spring 中,則使用 SqlSessionFactoryBean 來替代。
-- 官方文檔

那咱們下載MyBatis-Spring源碼 具體看看

查看大圖

image

SqlSessionFactoryBean實現了Spring 的3個重要接口:

  • InitializingBean
    接口由bean實現,當BeanFactory設置了它們的全部屬性後須要作出反應:例如,執行自定義初始化,或僅檢查是否已設置全部必需屬性。

關鍵代碼

/**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    //參數檢測
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
    //sqlSessionFactory 實例化
    this.sqlSessionFactory = buildSqlSessionFactory();
    
  }
  
  /**
   * Build a {@code SqlSessionFactory} instance.
   *
   * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
   * {@code SqlSessionFactory} instance based on an Reader.
   * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
   *
   * @return SqlSessionFactory
   * @throws IOException if loading the config file failed
   */
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    //省略 configuration 建立代碼
    
    //返回建立的SqlSessionFactory 
    return this.sqlSessionFactoryBuilder.build(configuration);
  }
  • FactoryBean
    用於建立複雜的Bean對象,通常的Bean能夠經過XML文件配置,但複雜Bean對象使用XML比較困難。

關鍵代碼

/**
   * {@inheritDoc}
   */
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    //返回建立好的SqlSessionFatory對象
    return this.sqlSessionFactory;
  }
  • ApplicationListener
    當ApplicationContext被初始化或刷新時引起的事件,當Spring容器徹底啓動後執行。

關鍵代碼

/**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      //檢測MyBatis全部配置文件語句是否完成
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
  }

6. 參考資料


關於MyBatis源碼解讀之SqlSession就介紹到這裏。若有疑問,歡迎留言,謝謝。

相關文章
相關標籤/搜索