SqlSession為什么可以提交事務


 

本應在開始讀MyBatis源碼時首先應該了解下MyBatis的SqlSession的四大對象:Executor、StatemenHandler、ParameterHandler、ResultHandler,但我想把這四大對象放到我們源碼中一步一步來解讀。

開始。


 對MyBatis的使用我們在最開始都已經知道可以通過xml配置文件的方式,也可以通過Java代碼創建Configuration對象的方式。這兩者實際上是一樣,xml配置文件的方式最終也是通過解析xml配置文件創建一個Configuration對象。可能對於很多人來說MyBatis通常是和Spring配合使用,用了N年MyBatis也不能把MyBatis說個所以出來。寫MyBatis的這個系列,正式希望不要只光會用,還要懂其原理,熟悉一個語言、一個框架的特性原理才能在不同場合使用不同的特性。

回到正題,我們說到使用MyBatis第一步就是配置,或者說第一個重要的對象就是Configuration。但我想要閱讀的第一個源碼並不是Configuration類,我們暫且知道它會貫穿整個MyBatis的生命周期,它是存放一些配置所在的地方即可。我們要說的是MyBatis第二個重要部分——SqlSession的執行過程。

從官方文檔的說明,我們可以知道SqlSession是由SqlSessionFactory創建,SqlSessionFactoryBuilder創建。

我們通過代碼簡單回顧一下SQLSession實例的創建過程。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

在創建一個SqlSession實例時,首先需要創建一個SqlSessionFactory實例,而又需要通過SqlSessionFactoryBuilder()的build靜態方法來創建SqlSessionFactory。(關於這三者的作用域(Scope)及生命周期之前有介紹過,這里不再多講,參考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

所以我們首先來看看SqlSessionFactoryBuilder這個類。它放置在package org.apache.ibatis.session包中。

復制代碼
 1 public class SqlSessionFactoryBuilder {
 2 
 3   public SqlSessionFactory build(Reader reader) {
 4     return build(reader, null, null);
 5   }
 6 
 7   public SqlSessionFactory build(Reader reader, String environment) {
 8     return build(reader, environment, null);
 9   }
10 
11   public SqlSessionFactory build(Reader reader, Properties properties) {
12     return build(reader, null, properties);
13   }
14 
15   public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
16     try {
17       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
18       return build(parser.parse());
19     } catch (Exception e) {
20       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
21     } finally {
22       ErrorContext.instance().reset();
23       try {
24         reader.close();
25       } catch (IOException e) {
26         // Intentionally ignore. Prefer previous error.
27       }
28     }
29   }
30 
31   public SqlSessionFactory build(InputStream inputStream) {
32     return build(inputStream, null, null);
33   }
34 
35   public SqlSessionFactory build(InputStream inputStream, String environment) {
36     return build(inputStream, environment, null);
37   }
38 
39   public SqlSessionFactory build(InputStream inputStream, Properties properties) {
40     return build(inputStream, null, properties);
41   }
42 
43   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
44     try {
45       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
46       return build(parser.parse());
47     } catch (Exception e) {
48       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
49     } finally {
50       ErrorContext.instance().reset();
51       try {
52         inputStream.close();
53       } catch (IOException e) {
54         // Intentionally ignore. Prefer previous error.
55       }
56     }
57   }
58     
59   public SqlSessionFactory build(Configuration config) {
60     return new DefaultSqlSessionFactory(config);
61   }
62 
63 }
復制代碼

我們可以看到這個類用很多的構造方法,但主要分為三大類:1、第3-29行是通過讀取字符流(Reader)的方式構件SqlSessionFactory。2、第31-57行是通過字節流(InputStream)的方式構件SqlSessionFacotry。3、第59行直接通過Configuration對象構建SqlSessionFactory。第1、2種方式是通過配置文件方式,第3種是通過Java代碼方式。

讓我們再仔細來看到底是怎么構建出SqlSessionFactory的呢?以通過InputStream字節流的方式來看,和它相關的一共有4個構造方法,其中最后一個public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties),第2個和第3個參數並不陌生,這相當於在告訴這兩個配置項environment、properties是可以通過在構建SqlSessionFactory的時候進行配置的或重新配置(此時優先級最高)。首先通過第45-46行代碼XMLConfigBuilder工具類對配置文件進行解析成Configuration對象,再調用public SqlSessionFactory build(Configuration config)構建出SqlSessionFactory,所以兜兜轉轉,不管是配置文件還是Java代碼,最后都會經過解析通過Configuration對象產生SqlSessionFactory。

我們可以發現第60行代碼返回的是DefaultSqlSessionFactory實例,而不是SqlSessionFactory。那是因為實際上SqlSessionFactory是一個接口,而DefaultSqlSessionFactory是它的實現類。如下圖所示。

在這里我們暫且不管SqlSessionManager,我們只需知道SqlSessionFactory有DefaultSqlSessionFactory和SqlSessionManager。在SqlSessionFactory可以猜測一下有什么方法。

回顧SqlSession的創建過程,其實我們也能猜測得到SqlSessionFactory一定主要是創建SqlSession實例的方法。

復制代碼
 1 public interface SqlSessionFactory {
 2 
 3   SqlSession openSession();
 4 
 5   SqlSession openSession(boolean autoCommit);
 6   SqlSession openSession(Connection connection);
 7   SqlSession openSession(TransactionIsolationLevel level);
 8 
 9   SqlSession openSession(ExecutorType execType);
10   SqlSession openSession(ExecutorType execType, boolean autoCommit);
11   SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
12   SqlSession openSession(ExecutorType execType, Connection connection);
13 
14   Configuration getConfiguration();
15 
16 }
復制代碼

這么多的openSession重載方法,都是通過傳入不同的參數構造SqlSession實例,有通過設置事務是否自動提交"autoCommit",有設置執行器類型"ExecutorType"來構造的,還有事務的隔離級別等等。最后一個方法就告訴我們可以通過SqlSessionFactory來獲取Configuration對象。至於DefaultSqlSessionFactory對SqlSessionFactory的具體實現,除了以上方法之外,還包括了:openSessionFromDataSource、openSessionFromConnection、getTransactionFactoryFromEnvironment、closeTransaction。到這里我們似乎還是只停留在表面,並沒有涉及相對比較底層的代碼啊,別急。我們這是剛走了一遍“SqlSession創建過程”的流程。下面我們從SqlSessionFactoryBuilder第60行return new DefaultSqlSessionFactory(config)開始。


 

由於SqlSessionFactory的實現類DefaultSqlSessionFactory,源碼過長,我們在其中以截取關鍵的代碼作為解讀。

DefaultSqlSessionFactory中的第1行代碼實際上就非常值得我們思考:final關鍵字。

private final Configuration configuration;

為什么會使用final關鍵字對Configuration對象進行修飾呢?Configuration應該是存在於MyBatis的整個生命周期那么意味着它應該是有且僅有一個實例的,而final關鍵字修飾的變量字段就代表它是不可變對象《“不可變的對象”與“不可變的對象引用”》),這也恰好能解釋說明官方User Guide中所說的SqlSessionFactory應該是單例的。但這是設計在前?還是規則在前呢?如果是設計在前,那為什么這樣設計?如果是規則在前,是什么樣的規則規定了這樣做呢?我認為是設計在前。

首先,MyBatis認為配置文件之所以是配置文件那么就以為着它只有一種配置(這個說法並不是很全面,因為我們已經見到了那么多的構造方法就說明在一個應用程序中可以通過不同的場景配置選用不同的配置,事實也如此),就好比我們將一個新手機買回來過后,設置時間、日期就不再去更改,但我們可能會出國,這個時候就要配置選用另一個時區的時間,不過我還是使用的是這個手機的設置,換句話說,你的手機不可能有兩個系統設置吧。所以Configuration對象實際上就是我們手機上的系統設置。而SqlSessionFactory是通過Configuration來構造SqlSession的,對Configuration的引用當然是不可變的,如果可變,那相當於你手機里豈不是可以新建一個系統設置?那不就亂套了?索性final,對象不可變。此時也就建議SqlSessionFactory是單例的了,你構建N個SqlSessionFactory,它們也是通過一個Configuration對象來構造的SqlSession實例,那還有必要有N個SqlSessionFactory了嗎?顯然沒有必要,所以最好就是將SqlSessionFactory設計為單例。同樣可參考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

這才對DefaultSqlSessionFactory類第一句話進行了解讀,接着就是實現SqlSessionFactory接口的8個構造方法。DefaultSqlSessionFactory並沒有直接實現這8個構造方法而是調用另外兩個新的方法,這8個構造方法實際上分為兩大類:一個是從數據源中獲取SqlSession,一個是從Connection中獲取SqlSession(包含Connection參數的那兩個構造函數)。

先看從數據源中獲取SqlSession。

復制代碼
 1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2     Transaction tx = null;
 3     try {
 4       final Environment environment = configuration.getEnvironment();
 5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 7       final Executor executor = configuration.newExecutor(tx, execType);
 8       return new DefaultSqlSession(configuration, executor, autoCommit);
 9     } catch (Exception e) {
10       closeTransaction(tx); // may have fetched a connection so lets call close()
11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
12     } finally {
13       ErrorContext.instance().reset();
14     }
15   }
復制代碼

如果沒有傳入ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit這三個參數就代表使用我們Configuration對象中的配置(看來Executor、TransactionIsolationLevel、autoCommit是可以靈活配置的)。第8行創建出一個DefaultSqlSession實例,可以猜測SqlSession是一個接口而DefaultSqlSession是其實現類。對於SqlSession的創建過程,我們馬上就要走到最后一步SqlSession的構建。而這也是最關鍵最重要最發雜的一步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM