MyBatis源碼解析之Transaction事務(wù)模塊
1、回顧
之前介紹了Environment環(huán)境類,這其實(shí)是一個(gè)單例類,在MyBatis運(yùn)行開(kāi)啟后只會(huì)存在一個(gè)唯一的環(huán)境實(shí)例,雖然我們可以在Configuration配置文件中配置多個(gè)環(huán)境,但是項(xiàng)目運(yùn)行中只會(huì)存在其中的一個(gè),一般項(xiàng)目會(huì)存在開(kāi)發(fā)環(huán)境和測(cè)試環(huán)境、生產(chǎn)環(huán)境三大環(huán)境,其是否可以設(shè)置到配置文件中,在開(kāi)發(fā)時(shí)使用開(kāi)發(fā)環(huán)境,測(cè)試時(shí)使用測(cè)試環(huán)境,正式運(yùn)營(yíng)時(shí)可以使用生產(chǎn)環(huán)境。
之前還提到Environment類中有三個(gè)字段,除了id之外,TransactionFactory和DataSource都是比較復(fù)雜的模塊,這一次我們介紹Transaction模塊(即事務(wù)模塊)。
2、事務(wù)模塊
事務(wù)模塊位于org.apache.ibatis.transaction包,這個(gè)包內(nèi)的類均是事務(wù)相關(guān)的類:
org.apache.ibatis.transaction
-----org.apache.ibatis.transaction.jdbc
----------JdbcTransaction.java
----------JdbcTransactionFactory.java
-----org.apache.ibatis.transaction.managed
----------ManagedTransaction.java
----------ManagedTransactionFactory.java
-----Transaction.java
-----TransactionException.java
-----TransactionFactory.java
從上面的類結(jié)構(gòu)中也能看出來(lái),MyBatis的事務(wù)模塊采用的是工廠模式。
2.1 事務(wù)接口
位于org.apache.ibatis.transaction包的Transaction和TransactionFactory都是接口類。
Transaction是事務(wù)接口,其中定義了四個(gè)方法:
commit()
-事務(wù)提交rollBack()
-事務(wù)回滾close()
-關(guān)閉數(shù)據(jù)庫(kù)連接getConnection()
-獲取數(shù)據(jù)庫(kù)連接
如下代碼所示:
package org.apache.ibatis.transaction; import java.sql.Connection; import java.sql.SQLException; /** * 事務(wù),包裝了一個(gè)Connection, 包含commit,rollback,close方法 * 在 MyBatis 中有兩種事務(wù)管理器類型(也就是 type=”[JDBC|MANAGED]”): */ public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
TransactionFactory是事務(wù)工廠接口,其中定義了三個(gè)方法:
setProperties(Properties props)
-設(shè)置屬性newTransaction(Connection conn)
-創(chuàng)建事務(wù)實(shí)例newTransaction(DataSource dataSource,TransactionIsolationLevel level,boolean autoCommit)
-創(chuàng)建事務(wù)實(shí)例
如下代碼所示:
package org.apache.ibatis.transaction; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.session.TransactionIsolationLevel; /** * 事務(wù)工廠 */ public interface TransactionFactory { //設(shè)置屬性 void setProperties(Properties props); //根據(jù)Connection創(chuàng)建Transaction Transaction newTransaction(Connection conn); //根據(jù)數(shù)據(jù)源和事務(wù)隔離級(jí)別創(chuàng)建Transaction Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); }
Transacrion接口定義的目的就是為了對(duì)具體的事務(wù)類型進(jìn)行抽象,便于擴(kuò)展;TransactionFactory與其一樣,是對(duì)事務(wù)工廠的抽象,同樣便于具體類型的事務(wù)工廠的擴(kuò)展實(shí)現(xiàn)。
2.2 MyBatis事務(wù)類型
說(shuō)到這里,就不得不提到MyBatis里內(nèi)置的兩種事務(wù)類型及對(duì)應(yīng)的事務(wù)工廠了,還記得在上一文中給出的environment配置信息,有這么一句:
<transactionManager type="JDBC"/>
這里的<transactionManager>標(biāo)簽就是用于定義項(xiàng)目所使用的事務(wù)類型,具體的類型由type屬性來(lái)指定,此處指定使用“JDBC”類型事務(wù),當(dāng)然MyBatis還提供了另外一種“MANAGED”型事務(wù)。
- ---JDBC
- ---MANAGED
這里的“JDBC”和“MANAGED”是在Configuration配置類的類型別名注冊(cè)器中注冊(cè)的別名,其對(duì)應(yīng)的類分別是:JdbcTransactionFactory.class和ManagedTransactionFactory.class。具體的配置如下所述:
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
上面的代碼是在Configuration類的無(wú)參構(gòu)造器中定義的,這里拿來(lái)僅用于展示,具體說(shuō)明以后會(huì)介紹。
這里提一句:類型別名注冊(cè)器額原理就是將別名與具體的類類型以鍵值對(duì)的方式保存到一個(gè)HashMap中,這樣只要知道別名(鍵),就可以從Map中得到對(duì)應(yīng)的值(Class類型),很簡(jiǎn)單!
現(xiàn)在只要知道MyBatis能夠根據(jù)你在配置文件中設(shè)置的事務(wù)類型,直接找到對(duì)應(yīng)的事務(wù)工廠類就行了。
下面對(duì)上面提到的兩種事務(wù)類型進(jìn)行解讀。
- ---JDBC事務(wù)模型:
JdbcTransaction
- ---MANAFED事務(wù)模型:
ManagedTransaction
二者的不同之處在于:前者是直接使用JDK提供的JDBC來(lái)管理事務(wù)的各個(gè)環(huán)節(jié):提交、回滾、關(guān)閉等操作,而后者則什么都不做,那么后者有什么意義呢,當(dāng)然很重要?!?/p>
當(dāng)我們單獨(dú)使用MyBatis來(lái)構(gòu)建項(xiàng)目時(shí),我們要在Configuration配置文件中進(jìn)行環(huán)境(environment)配置,在其中要設(shè)置事務(wù)類型為JDBC,意思是說(shuō)MyBatis被單獨(dú)使用時(shí)就需要使用JDBC類型的事務(wù)模型,因?yàn)樵谶@個(gè)模型中定義了事務(wù)的各個(gè)方面,使用它可以完成事務(wù)的各項(xiàng)操作。
而MANAGED類型的事務(wù)模型其實(shí)是一個(gè)托管模型,也就是說(shuō)它自身并不實(shí)現(xiàn)任何事務(wù)功能,而是托管出去由其他框架來(lái)實(shí)現(xiàn),你可能還不明白,這個(gè)事務(wù)的具體實(shí)現(xiàn)就交由如Spring之類的框架來(lái)實(shí)現(xiàn),而且在使用SSM整合框架后已經(jīng)不再需要單獨(dú)配置環(huán)境信息(包括事務(wù)配置與數(shù)據(jù)源配置),因?yàn)樵谠谡蟡ar包(mybatis-spring.jar)中擁有覆蓋mybatis里面的這部分邏輯的代碼,實(shí)際情況是即使你顯式設(shè)置了相關(guān)配置信息,系統(tǒng)也會(huì)視而不見(jiàn)......
托管的意義顯而易見(jiàn),正是為整合而設(shè)。
我們學(xué)習(xí)MyBatis的目的正是由于其靈活性和與Spring等框架的無(wú)縫整合的能力,所以有關(guān)JDBC事務(wù)模塊的內(nèi)容明顯不再是MyBatis功能中的重點(diǎn),也許只有在單獨(dú)使用MyBatis的少量系統(tǒng)中才會(huì)使用到。
2.3 JDBC事務(wù)模型
雖然JDBC事務(wù)類型很少使用到,但是作為MyBatis不可分割的一部分,我們還是需要進(jìn)行一定的了解,JDBC事務(wù)的實(shí)現(xiàn)是對(duì)JDK中提供的JDBC事務(wù)模塊的再封裝,以適用于MyBatis環(huán)境。
MyBatis中的JDBC事務(wù)模塊包括兩個(gè)部分,分別為JDBC事務(wù)工廠和JDBC事務(wù),整個(gè)事務(wù)模塊采用的是抽象工廠模式,那么對(duì)應(yīng)于每一項(xiàng)具體的事務(wù)處理模塊必然擁有自己的事務(wù)工廠,事務(wù)模塊實(shí)例通過(guò)事務(wù)工廠來(lái)創(chuàng)建(事務(wù)工廠將具體的事務(wù)實(shí)例的創(chuàng)建封裝起來(lái))。
首先我們來(lái)看JDBC事務(wù)工廠:JdbcTransactionFactory
JdbcTransactionFactory繼承自TransactionFactory接口,實(shí)現(xiàn)了其中的所有方法。分別為一個(gè)設(shè)置屬性的方法和兩個(gè)新建事務(wù)實(shí)例的方法(參數(shù)不同),內(nèi)容很簡(jiǎn)單,作用也很簡(jiǎn)單。
其中setProperties()方法用于設(shè)置屬性,這個(gè)方法在XMLConfigBuilder中解析事務(wù)標(biāo)簽時(shí)調(diào)用,用于解析事務(wù)標(biāo)簽的下級(jí)屬性標(biāo)簽<property>(一般情況下我們并不會(huì)進(jìn)行設(shè)置,但是如果我們進(jìn)行了設(shè)置,那就會(huì)覆蓋MyBatis中的默認(rèn)設(shè)置)之后將其設(shè)置到創(chuàng)建的事務(wù)實(shí)例中。然而針對(duì)JDBC事務(wù)模型,在事務(wù)工廠的設(shè)置屬性方法中沒(méi)有任何執(zhí)行代碼,也就說(shuō)明JDBC事務(wù)模塊并不支持設(shè)置屬性的功能,即使你在配置文件中設(shè)置的一些信息,也不會(huì)有任何作用。
那么這個(gè)方法有什么用呢?前面提到,這個(gè)設(shè)置用于覆蓋默認(rèn)設(shè)置,只是JDBC事務(wù)模塊并不支持而已,但并不代表別的事務(wù)模型不支持,同時(shí)這個(gè)方法也可用于功能擴(kuò)展。
另外兩個(gè)方法顯而易見(jiàn),就是用于創(chuàng)建JDBC事務(wù)實(shí)例的生產(chǎn)方法,只是參數(shù)不同,方法的重載而已。其中一個(gè)生產(chǎn)方法僅需傳遞一個(gè)實(shí)例Connection,這代表一個(gè)數(shù)據(jù)庫(kù)連接。而另一個(gè)方法需要傳遞三個(gè)參數(shù)(DataSource、TransactionIsolationLevel、boolean),其實(shí)這對(duì)應(yīng)于MyBatis中SqlSession的兩種生產(chǎn)方式,其參數(shù)與這里一一對(duì)應(yīng),這部分內(nèi)容以后介紹,此處不再贅述。
然后我們來(lái)看看JDBC事務(wù)類:JdbcTransaction
其中有四個(gè)參數(shù):
protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommmit;
這四個(gè)參數(shù)分別對(duì)應(yīng)事務(wù)工廠中的兩個(gè)生產(chǎn)方法中的總共四個(gè)參數(shù),對(duì)應(yīng)的在事務(wù)類中定義了兩個(gè)構(gòu)造器,構(gòu)造實(shí)例的同時(shí)進(jìn)行賦值:
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommmit = desiredAutoCommit; } public JdbcTransaction(Connection connection) { this.connection = connection; }
其次在該類中實(shí)現(xiàn)了Transaction接口,實(shí)現(xiàn)了其中的四個(gè)方法,三個(gè)功能性方法,和一個(gè)獲取數(shù)據(jù)庫(kù)連接的方法。三個(gè)功能方法分別是提交、回滾和關(guān)閉。
@Override public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); } } @Override public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); } } @Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } }
通過(guò)觀察這三個(gè)方法,可以發(fā)現(xiàn),其中都使用了connection來(lái)進(jìn)行具體操作,因此這些方法使用的前提就是先獲取connection數(shù)據(jù)庫(kù)連接,Connection的獲取使用getConnection()方法
@Override public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; }
在上面的方法中調(diào)用了openConnection()方法:
protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); }
可見(jiàn)connection是從數(shù)據(jù)源dataSource中獲取的,最后會(huì)調(diào)用setDesiredAutoCommit()方法:
protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // Only a very poorly implemented driver would fail here, // and there's not much we can do about that. throw new TransactionException("Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). " + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); } }
這個(gè)方法的目的就是為connection中的自動(dòng)提交賦值(真或假)。
這么看來(lái),我們創(chuàng)建事務(wù)實(shí)例所提供的三個(gè)參數(shù)就是為connection服務(wù)的,其中DataSource是用來(lái)獲取Connection實(shí)例的,而TransactionIsolationLevel(事務(wù)級(jí)別)和boolean(自動(dòng)提交)是用來(lái)填充connection的,通過(guò)三個(gè)參數(shù)我們獲得了一個(gè)圓滿的Connection實(shí)例,然后我們就可以使用這個(gè)實(shí)例來(lái)進(jìn)行事務(wù)操作:提交、回滾、關(guān)閉。
2.4 關(guān)于自動(dòng)提交
在之前的代碼中我們能看到在關(guān)閉操作之前調(diào)用了一個(gè)方法:resetAutoCommit():
protected void resetAutoCommit() { try { if (!connection.getAutoCommit()) { // MyBatis does not call commit/rollback on a connection if just selects were performed. // Some databases start transactions with select statements // and they mandate a commit/rollback before closing the connection. // A workaround is setting the autocommit to true before closing the connection. // Sybase throws an exception here. if (log.isDebugEnabled()) { log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(true); } } catch (SQLException e) { log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); } }
這里相對(duì)自動(dòng)提交做個(gè)解說(shuō),如果設(shè)置自動(dòng)提交為真,那么數(shù)據(jù)庫(kù)將會(huì)將每一個(gè)SQL語(yǔ)句當(dāng)做一個(gè)事務(wù)來(lái)執(zhí)行,為了將多條SQL當(dāng)做一個(gè)事務(wù)進(jìn)行提交,必須將自動(dòng)提交設(shè)置為false,然后進(jìn)行手動(dòng)提交。一般在我們的項(xiàng)目中,都需要將自動(dòng)提交設(shè)置為false,即將自動(dòng)提交關(guān)閉,使用手動(dòng)提交
這個(gè)方法中通過(guò)對(duì)connection實(shí)例中的自動(dòng)提交設(shè)置(真或假)進(jìn)行判斷,如果為false,表明不執(zhí)行自動(dòng)提交,則復(fù)位,重新將其設(shè)置為true。(自動(dòng)提交的默認(rèn)值為true)這個(gè)操作執(zhí)行在connection關(guān)閉之前。可以看做是連接關(guān)閉之前的復(fù)位操作。
2.5 問(wèn)題
在JdbcTransaction中提供的兩個(gè)構(gòu)造器中以Connection為參數(shù)的構(gòu)造器額作用是什么呢?
我們需要自動(dòng)組裝一個(gè)完整的Connection,以其為參數(shù)來(lái)生產(chǎn)一個(gè)事務(wù)實(shí)例。這用在什么場(chǎng)景中呢?
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring?JPA?deleteInBatch導(dǎo)致StackOverflow問(wèn)題
這篇文章主要介紹了Spring?JPA?deleteInBatch導(dǎo)致StackOverflow問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05spring boot項(xiàng)目使用@JsonFormat失效問(wèn)題的解決
這篇文章主要介紹了spring boot項(xiàng)目使用@JsonFormat失效問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11java實(shí)現(xiàn)學(xué)生成績(jī)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生成績(jī)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07IDEA生成servlet程序的實(shí)現(xiàn)步驟
這篇文章主要介紹了IDEA生成servlet程序的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03SpringBoot靜態(tài)資源與首頁(yè)配置實(shí)現(xiàn)原理深入分析
最近在做SpringBoot項(xiàng)目的時(shí)候遇到了“白頁(yè)”問(wèn)題,通過(guò)查資料對(duì)SpringBoot訪問(wèn)靜態(tài)資源做了總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10Java實(shí)現(xiàn)用位運(yùn)算維護(hù)狀態(tài)碼
位運(yùn)算是一種非常高效的運(yùn)算方式,在算法考察中比較常見(jiàn),那么業(yè)務(wù)代碼中我們?nèi)绾问褂梦贿\(yùn)算呢,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧2024-03-03解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問(wèn)題
這篇文章主要介紹了解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件
這篇文章主要介紹了詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12Java服務(wù)器主機(jī)信息監(jiān)控工具類的示例代碼
這篇文章主要介紹了Java服務(wù)器主機(jī)信息監(jiān)控工具類的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04