1. 程式人生 > >Mybatis詳解(二) sqlsession的建立過程

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

## 我們處於的位置 我們要清楚現在的情況. 現在我們已經呼叫了`SqlSessionFactoryBuilder`的build方法生成了`SqlSessionFactory `物件. 但是如標題所說,要想生成`sqlsession`還要另一步`SqlSessionFactory `呼叫`openSession()`方法生成`sqlsession`; 這就要從上一部分程式碼講起 上文講到 ![img](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtMTlkMzA5OTM5YjhiOGE5OC5wbmc?x-oss-process=image/format,png) 我們建立的實際上是一個叫做`DefaultSqlSessionFactory`的類,實際上他是一個`SqlSessionFactory`介面(沒錯,這玩應是介面)的實現類. 既然sqlsession是由opensession產生的,那我們就先看這個方法. ![img](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtYzA3YWZkY2RhODY3NWI4MC5wbmc?x-oss-process=image/format,png) 說一嘴題外話就是自動提交也是在這個部分設定的,下面是如果你設定了autocommit的情況. ```java public SqlSession openSession(boolean autoCommit) { //this.configuration.getDefaultExecutorType()值為 ExecutorType.SIMPLE; return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit); } ``` **引數中 configuration 獲取了預設的執行器 “SIMPLE”.** --- ## `DefaultSqlSessionFactory`類 呼叫了一個同一個類中`openSessionFromDataSource`方法. 在這個類中是如下執行流程 所要知道的一部分知識. [environments執行環境](https://www.cnblogs.com/hellowhy/p/9674280.html) MyBatis 核心配置綜述之 Configuration詳解 其實就是資料庫連線那個部分. ```java 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;`的屬性. 我們不必過於關注這個部分. ```java 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 `後,呼叫` JdbcTransactionFactory` 的` newTransaction `方法建立一個事務物件. 當然因為程式碼中採用`TransactionFactory `介面作為宣告物件.所以無論分之一傳回來的是哪個工廠物件.在分支二中都可以執行. 我們先講` JdbcTransactionFactory`的情況. 分支二中呼叫的是這個`newTransaction`方法.(還有一個過載的) ```java public Transaction newTransaction(Connection conn) { return new JdbcTransaction(conn); } ``` 這就到了另一個類中`JdbcTransaction`中. ## JdbcTransaction 我刪掉其中的實現程式碼 ```java 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](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtYzA5MjEyZGU2NDE2ZmY3Zi5wbmc?x-oss-process=image/format,png) 所以我們有理由認為,這個類就是對jdbc連線部分的封裝. **總結** 至此分支二結束,我們對於標籤在xml中的存在情況,會返回兩種截然不同物件.一種是作為jdbc連線封裝的`JdbcTransaction`物件.另一個則是`ManagedTransaction`物件(這個沒講....) --- ## 分支三 第三分支我們將回到`Configuration`物件. ## Configuration物件 法此時已經建立好事務物件。接下來將事務物件執行器作為引數執行 configuration 的 newExecutor 方法來獲取一個 執行器類。我們看看該方法實現: ![img](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtZjY3MjcyYmNjYzQzYmMwMi5wbmc?x-oss-process=image/format,png) 首先第一句將判斷是否傳入了一個excutorType引數,如果沒有就用預設的引數. 也就是 ExecutorType.SIMPLE(前面出現過),然後根據執行的型別來建立不同的執行器,預設是 SimpleExecutor 執行器. **Mybatis有三種基本的Executor執行器**: - SimpleExecutor:每執行一次update或select,就開啟一個Statement物件,用完立刻關閉Statement物件。 - ReuseExecutor:執行update或select,以sql作為key查詢Statement物件,存在就使用,不存在就建立,用完後,不關閉Statement物件,而是放置於Map內,供下一次使用。簡言之,就是重複使用Statement物件。 - BatchExecutor:執行update(沒有select,JDBC批處理不支援select),將所有sql都新增到批處理中(addBatch()),等待統一執行(executeBatch()),它快取了多個Statement物件,每個Statement物件都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同。 作用範圍:Executor的這些特點,都嚴格限制在SqlSession生命週期範圍內。 然後我們看下一句部分 ```java 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](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtYjU4Njc5MDk5MmNlYTM0MC5wbmc?x-oss-process=image/format,png) 該類包裝了事務物件,延遲載入的佇列,本地快取,永久快取,配置物件,還包裝了自己。 > 傳入的兩個引數分別為儲存了配置資訊的Configuration物件,以及封裝了jdbc中連線資料庫部分程式碼的`JdbcTransaction`物件. 回到 newExecutor 方法,判斷是否使用快取,預設是true, 則將剛剛的執行器包裝到新的 CachingExecutor 快取執行器中。最後將執行器新增到所有的攔截器中(如果配置了話),我們這裡沒有配置。 ![img](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtNDdlMzg2NGY0YjMyN2YyYi5wbmc?x-oss-process=image/format,png) 到此分支三結束 總結: 我們從用從分支二得到的物件,構建了一個執行器.這個執行物件,包括事務物件(即連jdbc連線部分的控制封裝.`JdbcTransaction`),延遲載入的佇列,本地快取,永久快取,配置物件(Configuration),還包裝了自己。 --- ## 四號分支 我們已經有了執行器,此時建立 DefaultSqlSession 物件,攜帶 configuration, executor, autoCommit 三個引數,該構造器就是簡單的賦值過程。我們有必要看看該類的結構: ![img](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQyMzY1NTMtMzBiNTc0NTUwODU2ZTkyYy5wbmc?x-oss-process=image/format,png) 該類包含了常用的所有方法,包括事務方法,可以說,該類封裝了執行器和事務類。而執行器才是具體的執行工作人員。 至此,我們已經完成了 SqlSession 的建立