Mybatis源碼解析(二) 創建SqlSession

前言

上篇文章講到了SqlSessionFactory的創建,這篇文章繼續對Mybatis的會話進行解析,那什麼是會話?
在這裏插入圖片描述
會話就是客戶端與數據庫建立的對話,一個Client和一個Database必須有一個會話,才能相互之間進行數據交互,它在Mybatis裏面對應的是SqlSession。

那麼哪些事情是和會話相關的?

  • 數據庫的增刪查改
  • 數據庫鏈接
  • 數據庫事務
  • 數據緩存

Mybatis是怎麼去實現上這些功能的?那麼接下來我們,就從代碼的角度去分析SqlSession

創建SqlSession

sqlSession操作數據庫流程
在這裏插入圖片描述
DefaultSqlSessionFactory通過openSession()方法創建DefaultSqlSession實例

public class DefaultSqlSessionFactory implements SqlSessionFactory {

 @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //1.獲取Environment配置
      final Environment environment = configuration.getEnvironment();
      //2.獲取TransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //3.從configuration裏面拿到Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //4.實例化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();
  }
}

上面的代碼做了4個動作:

  1. 獲取Environment配置
  2. 獲取TransactionFactory
  3. 從configuration裏面拿到Executor
  4. 實例化SqlSession

1.獲取Environment配置
通過configuration.getEnvironment()是從配置文件裏拿到Environment對象

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
 }

Environment對象裏面有三個字段成員,分別爲id,transactionFactory,dataSource。

id是environment節點的名字,transactionFactory是用來創建事務的工廠,dataSource是數據庫的鏈接,這3個成員在下圖中都能找到對應的配置在這裏插入圖片描述我們再回想一下,SqlSessionFactoryBuilder中的有個build()方法的重載,其中有個需要傳入environment的參數的,它的作用就是指定這個environment的id(Mybatis是可以配置多個environment,如果有配置多個則需要指定使用的是哪個)

2.獲取TransactionFactory
我們再看getTransactionFactoryFromEnvironment(environment)方法

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

“environment==null || environment.getTransactionFactory() == null” 用來判斷用戶配置TransactionFactory是否爲空,如果空的話返回默認的

public class ManagedTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new ManagedTransaction(ds, level, closeConnection);
  }
}

ManagedTransactionFactory.ManagedTransactionFactory對象創建的是ManagedTransaction事務,這個事務其實就是沒有事務,裏面並沒有commit和rolback實現(所以如果)
在這裏插入圖片描述

3.從configuration裏面拿到Executor
從configuration中拿到Executor對象(configuration其實也算是個工廠)

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Executor是執行器,是Mybatis真正用來執行操作的類,executorType參數是Executor的類型,從外面傳進來,如果用戶沒有指定是哪類執行器,SqlSessionFactory會從configuration.getDefaultExecutorType()中拿到默認的(Simple類型)

Executor有4種類型:

  • BatchExecutor可批量操作的
  • ReuseExecutor可重複使用的
  • SimpleExecutor默認的
  • CachingExecutor帶有二級緩存的

newExecutor()方法通過傳進來的executorType去實例化對應的Executor,如果配置了cacheEnabled=true的,還會將 實例化後的executor再包裝一下CachingExecutor。

4. 實例化SqlSession
SqlSession的創建是通過"new DefaultSqlSession(configuration, executor, autoCommit)"去實例化出來的,這裏傳了三個參數,分別爲configuration配置,executor執行器和autoCommit是否自動提交

public interface SqlSession extends Closeable {
  <T> T selectOne(String statement);
  <T> T selectOne(String statement, Object parameter);
  <E> List<E> selectList(String statement);
  <E> List<E> selectList(String statement, Object parameter);
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  <K, V> Map<K, V> selectMap(String statement, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
  <T> Cursor<T> selectCursor(String statement);
  <T> Cursor<T> selectCursor(String statement, Object parameter);
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
  void select(String statement, Object parameter, ResultHandler handler);
  void select(String statement, ResultHandler handler);
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  int insert(String statement);
  int insert(String statement, Object parameter);
  int update(String statement);
  int update(String statement, Object parameter);
  int delete(String statement);
  int delete(String statement, Object parameter);
  void commit();
  void commit(boolean force);
  void rollback();
  void rollback(boolean force);
  List<BatchResult> flushStatements();
  void close();
  void clearCache();
  Configuration getConfiguration();
  <T> T getMapper(Class<T> type);
  Connection getConnection();
}

從上面定義的接口上看,我們可以把它歸爲4類型:

  • 數據庫的增刪查改操作
  • 事務的提交和回滾
  • 數據連接的關閉
  • 緩存清理

接下來我們再看DefaultSqlSession的字段成員和構造函數

public class DefaultSqlSession implements SqlSession {

  //配置信息
  private final Configuration configuration;
  //執行器
  private final Executor executor;
  //是否自動提交
  private final boolean autoCommit;
  //是否變髒
  private boolean dirty;
  //遊標(作用是可以用來一條一條的查詢結果集)
  private List<Cursor<?>> cursorList;

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

DefaultSqlSession 有5個成員變量,其中configuration,executor,autoCommit都是從外面傳進來的,dirty的初始值爲false,是用來記錄sqlSession是否有數據未做提交(insert和update之後會改成true,然後 comit,rollback或close之後都會重新把它改爲false),cursorList是遊標的集合,DefaultSqlSession只有在執行selectCursor時纔會進行對它初始化(遊標是用於遍歷結果集的,每次只查一條數據).

接下來我們來看selectOne方法

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.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;
    }
  }

可以看到selectOne其實是特殊的selectList()方法,它會對結果集的條數作判斷,如果超過了一條會拋出異常,只有條數等於1的情況纔拿出第一條數據返回。(所以使用selectOne一定要保證查詢語句只會查出一條數據,如果有多條數據,Mybatis不會幫你做處理的)

  @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();
    }
  }
  
 @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();
    }
  }
  

上面的只列出了select()方法的兩個重載,一個有ResultHandler和一個沒有ResultHandler的,區別帶ResultHandler的是沒有返回參數,它需要通過ResultHandler去處理結果集, select()方法首先時獲取MappedStatement,MappedStatement是執行操作的內容的模板,Mybatis在通過SqlSessionFactoryBuider創建SqlSessionFactory時,就已經把所有的Mapper配置轉成MappedStatement並緩存到了Configuration了(我在上篇博客有介紹MappedStatement初始化的過程)

  public MappedStatement getMappedStatement(String id) {
    return this.getMappedStatement(id, true);
  }
  public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.get(id);
  }

configuration.getMappedStatement()的方法,是通過MappedStatement的id直接去configuration裏的Map緩存裏面拿的,拿到了MappedStatement之後,再把parameter通過包裝成數組的形式,最後交給executor的query()方法去執行(下一篇再介紹executor的原理)。executor的query()也有對應的重載,帶ResultHandler和不帶的,帶ResultHandler的方法是沒有返回值的,它直接是填充到ResultHandler裏面去。

插入,刪除,修改功能:

  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }
  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
  @Override
  public int update(String statement) {
    return update(statement, null);
  }
  @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();
    }
  }
  @Override
  public int delete(String statement) {
    return update(statement, null);
  }
  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

不管是insert、update、delete,在sqlSession裏面其實都是執行update操作,區分開來的作用只是sqlSession的模板的一種格式,和select類似,update()方法也是把執行的細節放在executor裏面,只不過executor.update()方法返回時的是int(影響條數)

除了增刪查改之外,sqlSession的其他方法

 @Override
  public void commit() {
    commit(false);
  }
 @Override
  public void commit(boolean force) {
    try {
      //執行提交,force爲true時強制提交,false需要判斷數據是後變髒和是否設置爲自動提交
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  @Override
  public void rollback() {
    rollback(false);
  }
  @Override
  public void rollback(boolean force) {
    try {
      //執行回滾,force爲true時強制提交,false需要判斷數據是後變髒和是否設置爲自動提交
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
   //判斷是否提交,force爲true時強制執行
  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }
  @Override
  public void clearCache() {
    //清除本地緩存
    executor.clearLocalCache();
  }

   @Override
  public void close() {
    try {
      //關閉連接
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }
  @Override
  public List<BatchResult> flushStatements() {
    try {
      //該方法針對批量操作的,批量操作時將statement緩存到一個list上,調用這個方法時才把list中的Statement提交到數據庫,提交完後再清空list
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

executor執行commit()、rollback()和close()時,會通過上面提到的TransactionFactory類,拿到具體的Transaction,再調用Transaction的commit、rollback和close方法

結語

SqlSession其實是一個模板方法,裏面封裝了用戶一些常用的操作,它的內部並沒有太多的執行細節,而是把複雜的處理邏輯放到了Executor裏面去

這篇對SqlSession介紹的博客到此爲止,後面會繼續分析Excutor的執行過程。

謝謝各位感興趣的小夥伴

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章