1、回顧
之前介紹了Environment環(huán)境類,這其實是一個單例類,在MyBatis運行開啟后只會存在一個唯一的環(huán)境實例,雖然我們可以在Configuration配置文件中配置多個環(huán)境,但是項目運行中只會存在其中的一個,一般項目會存在開發(fā)環(huán)境和測試環(huán)境、生產(chǎn)環(huán)境三大環(huán)境,其是否可以設置到配置文件中,在開發(fā)時使用開發(fā)環(huán)境,測試時使用測試環(huán)境,正式運營時可以使用生產(chǎn)環(huán)境。
之前還提到Environment類中有三個字段,除了id之外,TransactionFactory和DataSource都是比較復雜的模塊,這一次我們介紹Transaction模塊(即事務模塊)。
2、事務模塊
事務模塊位于org.apache.ibatis.transaction包,這個包內(nèi)的類均是事務相關(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的事務模塊采用的是工廠模式。
2.1 事務接口
位于org.apache.ibatis.transaction包的Transaction和TransactionFactory都是接口類。
Transaction是事務接口,其中定義了四個方法:
commit()-事務提交
rollBack()-事務回滾
close()-關(guān)閉數(shù)據(jù)庫連接
getConnection()-獲取數(shù)據(jù)庫連接
如下代碼所示:
1 package org.apache.ibatis.transaction; 2 import java.sql.Connection; 3 import java.sql.SQLException; 4 /** 5 * 事務,包裝了一個Connection, 包含commit,rollback,close方法 6 * 在 MyBatis 中有兩種事務管理器類型(也就是 type=”[JDBC|MANAGED]”): 7 */ 8 public interface Transaction { 9 Connection getConnection() throws SQLException;10 void commit() throws SQLException;11 void rollback() throws SQLException;12 void close() throws SQLException;13 }
TransactionFactory是事務工廠接口,其中定義了三個方法:
setProperties(Properties props)-設置屬性
newTransaction(Connection conn)-創(chuàng)建事務實例
newTransaction(DataSource dataSource,TransactionIsolationLevel level,boolean autoCommit)-創(chuàng)建事務實例
如下代碼所示:
1 package org.apache.ibatis.transaction; 2 import java.sql.Connection; 3 import java.util.Properties; 4 import javax.sql.DataSource; 5 import org.apache.ibatis.session.TransactionIsolationLevel; 6 /** 7 * 事務工廠 8 */ 9 public interface TransactionFactory {10 //設置屬性11 void setProperties(Properties props);12 //根據(jù)Connection創(chuàng)建Transaction13 Transaction newTransaction(Connection conn);14 //根據(jù)數(shù)據(jù)源和事務隔離級別創(chuàng)建Transaction15 Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);16 }
Transacrion接口定義的目的就是為了對具體的事務類型進行抽象,便于擴展;TransactionFactory與其一樣,是對事務工廠的抽象,同樣便于具體類型的事務工廠的擴展實現(xiàn)。
2.2 MyBatis事務類型
說到這里,就不得不提到MyBatis里內(nèi)置的兩種事務類型及對應的事務工廠了,還記得在上一文中給出的environment配置信息,有這么一句:
<transactionManager type="JDBC"/>
這里的<transactionManager>標簽就是用于定義項目所使用的事務類型,具體的類型由type屬性來指定,此處指定使用“JDBC”類型事務,當然MyBatis還提供了另外一種“MANAGED”型事務。
---JDBC
---MANAGED
這里的“JDBC”和“MANAGED”是在Configuration配置類的類型別名注冊器中注冊的別名,其對應的類分別是:JdbcTransactionFactory.class和ManagedTransactionFactory.class。具體的配置如下所述:
1 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);2 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
上面的代碼是在Configuration類的無參構(gòu)造器中定義的,這里拿來僅用于展示,具體說明以后會介紹。
這里提一句:類型別名注冊器額原理就是將別名與具體的類類型以鍵值對的方式保存到一個HashMap中,這樣只要知道別名(鍵),就可以從Map中得到對應的值(Class類型),很簡單!
現(xiàn)在只要知道MyBatis能夠根據(jù)你在配置文件中設置的事務類型,直接找到對應的事務工廠類就行了。
下面對上面提到的兩種事務類型進行解讀。
---JDBC事務模型:JdbcTransaction
---MANAFED事務模型:ManagedTransaction
二者的不同之處在于:前者是直接使用JDK提供的JDBC來管理事務的各個環(huán)節(jié):提交、回滾、關(guān)閉等操作,而后者則什么都不做,那么后者有什么意義呢,當然很重要。
當我們單獨使用MyBatis來構(gòu)建項目時,我們要在Configuration配置文件中進行環(huán)境(environment)配置,在其中要設置事務類型為JDBC,意思是說MyBatis被單獨使用時就需要使用JDBC類型的事務模型,因為在這個模型中定義了事務的各個方面,使用它可以完成事務的各項操作。而MANAGED類型的事務模型其實是一個托管模型,也就是說它自身并不實現(xiàn)任何事務功能,而是托管出去由其他框架來實現(xiàn),你可能還不明白,這個事務的具體實現(xiàn)就交由如Spring之類的框架來實現(xiàn),而且在使用SSM整合框架后已經(jīng)不再需要單獨配置環(huán)境信息(包括事務配置與數(shù)據(jù)源配置),因為在在整合jar包(mybatis-spring.jar)中擁有覆蓋mybatis里面的這部分邏輯的代碼,實際情況是即使你顯式設置了相關(guān)配置信息,系統(tǒng)也會視而不見......
托管的意義顯而易見,正是為整合而設。
我們學習MyBatis的目的正是由于其靈活性和與Spring等框架的無縫整合的能力,所以有關(guān)JDBC事務模塊的內(nèi)容明顯不再是MyBatis功能中的重點,也許只有在單獨使用MyBatis的少量系統(tǒng)中才會使用到。
2.3 JDBC事務模型
雖然JDBC事務類型很少使用到,但是作為MyBatis不可分割的一部分,我們還是需要進行一定的了解,JDBC事務的實現(xiàn)是對JDK中提供的JDBC事務模塊的再封裝,以適用于MyBatis環(huán)境。
MyBatis中的JDBC事務模塊包括兩個部分,分別為JDBC事務工廠和JDBC事務,整個事務模塊采用的是抽象工廠模式,那么對應于每一項具體的事務處理模塊必然擁有自己的事務工廠,事務模塊實例通過事務工廠來創(chuàng)建(事務工廠將具體的事務實例的創(chuàng)建封裝起來)。
首先我們來看JDBC事務工廠:JdbcTransactionFactory
JdbcTransactionFactory繼承自TransactionFactory接口,實現(xiàn)了其中的所有方法。分別為一個設置屬性的方法和兩個新建事務實例的方法(參數(shù)不同),內(nèi)容很簡單,作用也很簡單。
其中setProperties()方法用于設置屬性,這個方法在XMLConfigBuilder中解析事務標簽時調(diào)用,用于解析事務標簽的下級屬性標簽<property>(一般情況下我們并不會進行設置,但是如果我們進行了設置,那就會覆蓋MyBatis中的默認設置)之后將其設置到創(chuàng)建的事務實例中。然而針對JDBC事務模型,在事務工廠的設置屬性方法中沒有任何執(zhí)行代碼,也就說明JDBC事務模塊并不支持設置屬性的功能,即使你在配置文件中設置的一些信息,也不會有任何作用。
那么這個方法有什么用呢?前面提到,這個設置用于覆蓋默認設置,只是JDBC事務模塊并不支持而已,但并不代表別的事務模型不支持,同時這個方法也可用于功能擴展。
另外兩個方法顯而易見,就是用于創(chuàng)建JDBC事務實例的生產(chǎn)方法,只是參數(shù)不同,方法的重載而已。其中一個生產(chǎn)方法僅需傳遞一個實例Connection,這代表一個數(shù)據(jù)庫連接。而另一個方法需要傳遞三個參數(shù)(DataSource、TransactionIsolationLevel、boolean),其實這對應于MyBatis中SqlSession的兩種生產(chǎn)方式,其參數(shù)與這里一一對應,這部分內(nèi)容以后介紹,此處不再贅述。
然后我們來看看JDBC事務類:JdbcTransaction
其中有四個參數(shù):
1 protected Connection connection;2 protected DataSource dataSource;3 protected TransactionIsolationLevel level;4 protected boolean autoCommmit;
這四個參數(shù)分別對應事務工廠中的兩個生產(chǎn)方法中的總共四個參數(shù),對應的在事務類中定義了兩個構(gòu)造器,構(gòu)造實例的同時進行賦值:
1 public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {2 dataSource = ds;3 level = desiredLevel;4 autoCommmit = desiredAutoCommit;5 }6 public JdbcTransaction(Connection connection) {7 this.connection = connection;8 }
其次在該類中實現(xiàn)了Transaction接口,實現(xiàn)了其中的四個方法,三個功能性方法,和一個獲取數(shù)據(jù)庫連接的方法。三個功能方法分別是提交、回滾和關(guān)閉。
1 @Override 2 public void commit() throws SQLException { 3 if (connection != null && !connection.getAutoCommit()) { 4 if (log.isDebugEnabled()) { 5 log.debug("Committing JDBC Connection [" + connection + "]"); 6 } 7 connection.commit(); 8 } 9 }10 11 @Override12 public void rollback() throws SQLException {13 if (connection != null && !connection.getAutoCommit()) {14 if (log.isDebugEnabled()) {15 log.debug("Rolling back JDBC Connection [" + connection + "]");16 }17 connection.rollback();18 }19 }20 21 @Override22 public void close() throws SQLException {23 if (connection != null) {24 resetAutoCommit();25 if (log.isDebugEnabled()) {26 log.debug("Closing JDBC Connection [" + connection + "]");27 }28 connection.close();29 }30 }
通過觀察這三個方法,可以發(fā)現(xiàn),其中都使用了connection來進行具體操作,因此這些方法使用的前提就是先獲取connection數(shù)據(jù)庫連接,Connection的獲取使用getConnection()方法
1 @Override2 public Connection getConnection() throws SQLException {3 if (connection == null) {4 openConnection();5 }6 return connection;7 }
在上面的方法中調(diào)用了openConnection()方法:
1 protected void openConnection() throws SQLException { 2 if (log.isDebugEnabled()) { 3 log.debug("Opening JDBC Connection"); 4 } 5 connection = dataSource.getConnection(); 6 if (level != null) { 7 connection.setTransactionIsolation(level.getLevel()); 8 } 9 setDesiredAutoCommit(autoCommmit);10 }
可見connection是從數(shù)據(jù)源dataSource中獲取的,最后會調(diào)用setDesiredAutoCommit()方法:
1 protected void setDesiredAutoCommit(boolean desiredAutoCommit) { 2 try { 3 if (connection.getAutoCommit() != desiredAutoCommit) { 4 if (log.isDebugEnabled()) { 5 log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); 6 } 7 connection.setAutoCommit(desiredAutoCommit); 8 } 9 } catch (SQLException e) {10 // Only a very poorly implemented driver would fail here,11 // and there's not much we can do about that.12 throw new TransactionException("Error configuring AutoCommit. "13 + "Your driver may not support getAutoCommit() or setAutoCommit(). "14 + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);15 }16 }
這個方法的目的就是為connection中的自動提交賦值(真或假)。
這么看來,我們創(chuàng)建事務實例所提供的三個參數(shù)就是為connection服務的,其中DataSource是用來獲取Connection實例的,而TransactionIsolationLevel(事務級別)和boolean(自動提交)是用來填充connection的,通過三個參數(shù)我們獲得了一個圓滿的Connection實例,然后我們就可以使用這個實例來進行事務操作:提交、回滾、關(guān)閉。
2.4 關(guān)于自動提交
在之前的代碼中我們能看到在關(guān)閉操作之前調(diào)用了一個方法:resetAutoCommit():
1 protected void resetAutoCommit() { 2 try { 3 if (!connection.getAutoCommit()) { 4 // MyBatis does not call commit/rollback on a connection if just selects were performed. 5 // Some databases start transactions with select statements 6 // and they mandate a commit/rollback before closing the connection. 7 // A workaround is setting the autocommit to true before closing the connection. 8 // Sybase throws an exception here. 9 if (log.isDebugEnabled()) {10 log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");11 }12 connection.setAutoCommit(true);13 }14 } catch (SQLException e) {15 log.debug("Error resetting autocommit to true "16 + "before closing the connection. Cause: " + e);17 }18 }
這里相對自動提交做個解說,如果設置自動提交為真,那么數(shù)據(jù)庫將會將每一個SQL語句當做一個事務來執(zhí)行,為了將多條SQL當做一個事務進行提交,必須將自動提交設置為false,然后進行手動提交。一般在我們的項目中,都需要將自動提交設置為false,即將自動提交關(guān)閉,使用手動提交
這個方法中通過對connection實例中的自動提交設置(真或假)進行判斷,如果為false,表明不執(zhí)行自動提交,則復位,重新將其設置為true。(自動提交的默認值為true)這個操作執(zhí)行在connection關(guān)閉之前??梢钥醋鍪沁B接關(guān)閉之前的復位操作。
2.5 問題
在JdbcTransaction中提供的兩個構(gòu)造器中以Connection為參數(shù)的構(gòu)造器額作用是什么呢?
我們需要自動組裝一個完整的Connection,以其為參數(shù)來生產(chǎn)一個事務實例。這用在什么場景中呢?