Mybatis詳解(二) sqlsession的建立過程

TimothyRasinski發表於2020-06-12

我們處於的位置

我們要清楚現在的情況.

現在我們已經呼叫了SqlSessionFactoryBuilder的build方法生成了SqlSessionFactory 物件.

但是如標題所說,要想生成sqlsession還要另一步SqlSessionFactory 呼叫openSession()方法生成sqlsession;

這就要從上一部分程式碼講起

上文講到

img

我們建立的實際上是一個叫做DefaultSqlSessionFactory的類,實際上他是一個SqlSessionFactory介面(沒錯,這玩應是介面)的實現類.

既然sqlsession是由opensession產生的,那我們就先看這個方法.

img

說一嘴題外話就是自動提交也是在這個部分設定的,下面是如果你設定了autocommit的情況.

public SqlSession openSession(boolean autoCommit) {
  //this.configuration.getDefaultExecutorType()值為 ExecutorType.SIMPLE;
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}

引數中 configuration 獲取了預設的執行器 “SIMPLE”.


DefaultSqlSessionFactory

呼叫了一個同一個類中openSessionFromDataSource方法.

在這個類中是如下執行流程

所要知道的一部分知識.

environments執行環境

MyBatis 核心配置綜述之 Configuration詳解

其實就是資料庫連線那個部分.

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //從configuration物件中得到環境配置的物件
    final Environment environment = configuration.getEnvironment();
    //這個物件被用來建立一個事務工廠->一號分支
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  //事務工廠建立一個事務物件->二號分支
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //而 configurationye 則會根據事務物件和執行器型別建立一個執行器。
    ->三號分支
    final Executor executor = configuration.newExecutor(tx, execType);
    //返回一個預設的DefaultSqlSession物件
    ->四號分支
    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();
  }
}

現在我們要從一號分支開始


一號分支

final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

這個程式碼如下:

我們發現有兩種可能性.

如果傳進來的值沒有設定標籤那麼他會執行ManagedTransactionFactory()而反之則會執行environment.getTransactionFactory()

這兩者產生的物件都實現了 TransactionFactory介面.

這裡ManagedTransactionFactory()是沒有標籤時生成的物件.其核心就是一句

private boolean closeConnection = true;的屬性.

我們不必過於關注這個部分.

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
  if (environment == null || environment.getTransactionFactory() == null) {
   //如果沒有目標標籤
    return new ManagedTransactionFactory();
  }
  //如果有目標標籤
  return environment.getTransactionFactory();
}

environment.getTransactionFactory()產生的東西才是重點.

呼叫環境物件的getTransactionFactory方法,該方法和我們配置的一樣返回了一個 JdbcTransactionFactory,而實際上,TransactionFactory 只有2個實現類,一個是 ManagedTransactionFactory (沒有標籤時返回的),一個是 JdbcTransactionFactory(有標籤時返回的)。

至此一號分支結束,從此看來,一號分支實際上是將environment物件包裝成一個工廠物件.

請返回一號分支之前部分繼續.


分支二

tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

我們回到openSessionFromDataSource方法,獲取了 JdbcTransactionFactory 後,呼叫 JdbcTransactionFactorynewTransaction方法建立一個事務物件.

當然因為程式碼中採用TransactionFactory 介面作為宣告物件.所以無論分之一傳回來的是哪個工廠物件.在分支二中都可以執行.

我們先講 JdbcTransactionFactory的情況.

分支二中呼叫的是這個newTransaction方法.(還有一個過載的)

public Transaction newTransaction(Connection conn) {
  return new JdbcTransaction(conn);
}

這就到了另一個類中JdbcTransaction中.

JdbcTransaction

我刪掉其中的實現程式碼

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommmit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommmit = desiredAutoCommit;
  }

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  public Connection getConnection() throws SQLException {
  
  }

  public void commit() throws SQLException {
   
  }

  public void rollback() throws SQLException {
    
  }

  public void close() throws SQLException {
    
  }

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
   
  }

  protected void resetAutoCommit() {
    
  }

  protected void openConnection() throws SQLException {
   
  }

}

其實只要看了程式碼你就會發現,這個類中的方法,和我們呼叫session的方法高度重合.比如commit,rollback等等.而且還能設定事務的隔離級別

img

所以我們有理由認為,這個類就是對jdbc連線部分的封裝.

總結

至此分支二結束,我們對於標籤在xml中的存在情況,會返回兩種截然不同物件.一種是作為jdbc連線封裝的JdbcTransaction物件.另一個則是ManagedTransaction物件(這個沒講....)


分支三

第三分支我們將回到Configuration物件.

Configuration物件

法此時已經建立好事務物件。接下來將事務物件執行器作為引數執行 configuration 的 newExecutor 方法來獲取一個 執行器類。我們看看該方法實現:

img

首先第一句將判斷是否傳入了一個excutorType引數,如果沒有就用預設的引數.

也就是 ExecutorType.SIMPLE(前面出現過),然後根據執行的型別來建立不同的執行器,預設是 SimpleExecutor 執行器.

Mybatis有三種基本的Executor執行器:

  • SimpleExecutor:每執行一次update或select,就開啟一個Statement物件,用完立刻關閉Statement物件。

  • ReuseExecutor:執行update或select,以sql作為key查詢Statement物件,存在就使用,不存在就建立,用完後,不關閉Statement物件,而是放置於Map<String, Statement>內,供下一次使用。簡言之,就是重複使用Statement物件。

  • BatchExecutor:執行update(沒有select,JDBC批處理不支援select),將所有sql都新增到批處理中(addBatch()),等待統一執行(executeBatch()),它快取了多個Statement物件,每個Statement物件都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同。

作用範圍:Executor的這些特點,都嚴格限制在SqlSession生命週期範圍內。

然後我們看下一句部分

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

我們先將 BatchExecutor執行器.

img

該類包裝了事務物件,延遲載入的佇列,本地快取,永久快取,配置物件,還包裝了自己。

傳入的兩個引數分別為儲存了配置資訊的Configuration物件,以及封裝了jdbc中連線資料庫部分程式碼的JdbcTransaction物件.

回到 newExecutor 方法,判斷是否使用快取,預設是true, 則將剛剛的執行器包裝到新的 CachingExecutor 快取執行器中。最後將執行器新增到所有的攔截器中(如果配置了話),我們這裡沒有配置。

img

到此分支三結束

總結:

我們從用從分支二得到的物件,構建了一個執行器.這個執行物件,包括事務物件(即連jdbc連線部分的控制封裝.JdbcTransaction),延遲載入的佇列,本地快取,永久快取,配置物件(Configuration),還包裝了自己。


四號分支

我們已經有了執行器,此時建立 DefaultSqlSession 物件,攜帶 configuration, executor, autoCommit 三個引數,該構造器就是簡單的賦值過程。我們有必要看看該類的結構:

img

該類包含了常用的所有方法,包括事務方法,可以說,該類封裝了執行器和事務類。而執行器才是具體的執行工作人員。

至此,我們已經完成了 SqlSession 的建立過程。

相關文章