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