SqlSession爲何能夠提交事務

 

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

開始。sql


 對MyBatis的使用咱們在最開始都已經知道能夠經過xml配置文件的方式,也能夠經過Java代碼建立Configuration對象的方式。這二者其實是同樣,xml配置文件的方式最終也是經過解析xml配置文件建立一個Configuration對象。可能對於不少人來講MyBatis一般是和Spring配合使用,用了N年MyBatis也不能把MyBatis說個因此出來。寫MyBatis的這個系列,正式但願不要只光會用,還要懂其原理,熟悉一個語言、一個框架的特性原理才能在不一樣場合使用不一樣的特性。apache

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

從官方文檔的說明,咱們能夠知道SqlSession是由SqlSessionFactory建立,SqlSessionFactoryBuilder建立。框架

咱們經過代碼簡單回顧一下SQLSession實例的建立過程。ide

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 }
複製代碼

咱們能夠看到這個類用不少的構造方法,但主要分爲三大類:一、第3-29行是經過讀取字符流(Reader)的方式構件SqlSessionFactory。二、第31-57行是經過字節流(InputStream)的方式構件SqlSessionFacotry。三、第59行直接經過Configuration對象構建SqlSessionFactory。第一、2種方式是經過配置文件方式,第3種是經過Java代碼方式。fetch

讓咱們再仔細來看究竟是怎麼構建出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。ui

咱們能夠發現第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的構建。而這也是最關鍵最重要最發雜的一步。

相關文章
相關標籤/搜索