1、回顧
之前介紹了Environment環境類,這其實是一個單例類,在MyBatis運行開啓後只會存在一個唯一的環境實例,雖然我們可以在Configuration配置文件中配置多個環境,但是項目運行中只會存在其中的一個,一般項目會存在開發環境和測試環境、生產環境三大環境,其是否可以設置到配置文件中,在開發時使用開發環境,測試時使用測試環境,正式運營時可以使用生產環境。
之前還提到Environment類中有三個字段,除了id之外,TransactionFactory和DataSource都是比較複雜的模塊,這一次我們介紹Transaction模塊(即事務模塊)。
2、事務模塊
事務模塊位於org.apache.ibatis.transaction包,這個包內的類均是事務相關的類:
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
從上面的類結構中也能看出來,MyBatis的事務模塊採用的是工廠模式。
2.1 事務接口
位於org.apache.ibatis.transaction包的Transaction和TransactionFactory都是接口類。
Transaction是事務接口,其中定義了四個方法:
commit()-事務提交
rollBack()-事務回滾
close()-關閉數據庫連接
getConnection()-獲取數據庫連接
如下代碼所示:
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)-創建事務實例
newTransaction(DataSource dataSource,TransactionIsolationLevel level,boolean autoCommit)-創建事務實例
如下代碼所示:
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 //根據Connection創建Transaction
13 Transaction newTransaction(Connection conn);
14 //根據數據源和事務隔離級別創建Transaction
15 Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
16 }
Transacrion接口定義的目的就是爲了對具體的事務類型進行抽象,便於擴展;TransactionFactory與其一樣,是對事務工廠的抽象,同樣便於具體類型的事務工廠的擴展實現。
2.2 MyBatis事務類型
說到這裏,就不得不提到MyBatis裏內置的兩種事務類型及對應的事務工廠了,還記得在上一文中給出的environment配置信息,有這麼一句:
<transactionManager type="JDBC"/>
這裏的<transactionManager>標籤就是用於定義項目所使用的事務類型,具體的類型由type屬性來指定,此處指定使用“JDBC”類型事務,當然MyBatis還提供了另外一種“MANAGED”型事務。
---JDBC
---MANAGED
這裏的“JDBC”和“MANAGED”是在Configuration配置類的類型別名註冊器中註冊的別名,其對應的類分別是:JdbcTransactionFactory.class和ManagedTransactionFactory.class。具體的配置如下所述:
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
上面的代碼是在Configuration類的無參構造器中定義的,這裏拿來僅用於展示,具體說明以後會介紹。
這裏提一句:類型別名註冊器額原理就是將別名與具體的類類型以鍵值對的方式保存到一個HashMap中,這樣只要知道別名(鍵),就可以從Map中得到對應的值(Class類型),很簡單!
現在只要知道MyBatis能夠根據你在配置文件中設置的事務類型,直接找到對應的事務工廠類就行了。
下面對上面提到的兩種事務類型進行解讀。
---JDBC事務模型:JdbcTransaction
---MANAFED事務模型:ManagedTransaction
二者的不同之處在於:前者是直接使用JDK提供的JDBC來管理事務的各個環節:提交、回滾、關閉等操作,而後者則什麼都不做,那麼後者有什麼意義呢,當然很重要。
當我們單獨使用MyBatis來構建項目時,我們要在Configuration配置文件中進行環境(environment)配置,在其中要設置事務類型爲JDBC,意思是說MyBatis被單獨使用時就需要使用JDBC類型的事務模型,因爲在這個模型中定義了事務的各個方面,使用它可以完成事務的各項操作。而MANAGED類型的事務模型其實是一個託管模型,也就是說它自身並不實現任何事務功能,而是託管出去由其他框架來實現,你可能還不明白,這個事務的具體實現就交由如Spring之類的框架來實現,而且在使用SSM整合框架後已經不再需要單獨配置環境信息(包括事務配置與數據源配置),因爲在在整合jar包(mybatis-spring.jar)中擁有覆蓋mybatis裏面的這部分邏輯的代碼,實際情況是即使你顯式設置了相關配置信息,系統也會視而不見......
託管的意義顯而易見,正是爲整合而設。
我們學習MyBatis的目的正是由於其靈活性和與Spring等框架的無縫整合的能力,所以有關JDBC事務模塊的內容明顯不再是MyBatis功能中的重點,也許只有在單獨使用MyBatis的少量系統中才會使用到。
2.3 JDBC事務模型
雖然JDBC事務類型很少使用到,但是作爲MyBatis不可分割的一部分,我們還是需要進行一定的瞭解,JDBC事務的實現是對JDK中提供的JDBC事務模塊的再封裝,以適用於MyBatis環境。
MyBatis中的JDBC事務模塊包括兩個部分,分別爲JDBC事務工廠和JDBC事務,整個事務模塊採用的是抽象工廠模式,那麼對應於每一項具體的事務處理模塊必然擁有自己的事務工廠,事務模塊實例通過事務工廠來創建(事務工廠將具體的事務實例的創建封裝起來)。
首先我們來看JDBC事務工廠:JdbcTransactionFactory
JdbcTransactionFactory繼承自TransactionFactory接口,實現了其中的所有方法。分別爲一個設置屬性的方法和兩個新建事務實例的方法(參數不同),內容很簡單,作用也很簡單。
其中setProperties()方法用於設置屬性,這個方法在XMLConfigBuilder中解析事務標籤時調用,用於解析事務標籤的下級屬性標籤<property>(一般情況下我們並不會進行設置,但是如果我們進行了設置,那就會覆蓋MyBatis中的默認設置)之後將其設置到創建的事務實例中。然而針對JDBC事務模型,在事務工廠的設置屬性方法中沒有任何執行代碼,也就說明JDBC事務模塊並不支持設置屬性的功能,即使你在配置文件中設置的一些信息,也不會有任何作用。
那麼這個方法有什麼用呢?前面提到,這個設置用於覆蓋默認設置,只是JDBC事務模塊並不支持而已,但並不代表別的事務模型不支持,同時這個方法也可用於功能擴展。
另外兩個方法顯而易見,就是用於創建JDBC事務實例的生產方法,只是參數不同,方法的重載而已。其中一個生產方法僅需傳遞一個實例Connection,這代表一個數據庫連接。而另一個方法需要傳遞三個參數(DataSource、TransactionIsolationLevel、boolean),其實這對應於MyBatis中SqlSession的兩種生產方式,其參數與這裏一一對應,這部分內容以後介紹,此處不再贅述。
然後我們來看看JDBC事務類:JdbcTransaction
其中有四個參數:
1 protected Connection connection;
2 protected DataSource dataSource;
3 protected TransactionIsolationLevel level;
4 protected boolean autoCommmit;
這四個參數分別對應事務工廠中的兩個生產方法中的總共四個參數,對應的在事務類中定義了兩個構造器,構造實例的同時進行賦值:
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 }
其次在該類中實現了Transaction接口,實現了其中的四個方法,三個功能性方法,和一個獲取數據庫連接的方法。三個功能方法分別是提交、回滾和關閉。
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 @Override
12 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 @Override
22 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 }
通過觀察這三個方法,可以發現,其中都使用了connection來進行具體操作,因此這些方法使用的前提就是先獲取connection數據庫連接,Connection的獲取使用getConnection()方法
1 @Override
2 public Connection getConnection() throws SQLException {
3 if (connection == null) {
4 openConnection();
5 }
6 return connection;
7 }
在上面的方法中調用了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是從數據源dataSource中獲取的,最後會調用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中的自動提交賦值(真或假)。
這麼看來,我們創建事務實例所提供的三個參數就是爲connection服務的,其中DataSource是用來獲取Connection實例的,而TransactionIsolationLevel(事務級別)和boolean(自動提交)是用來填充connection的,通過三個參數我們獲得了一個圓滿的Connection實例,然後我們就可以使用這個實例來進行事務操作:提交、回滾、關閉。
2.4 關於自動提交
在之前的代碼中我們能看到在關閉操作之前調用了一個方法: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 }
這裏相對自動提交做個解說,如果設置自動提交爲真,那麼數據庫將會將每一個SQL語句當做一個事務來執行,爲了將多條SQL當做一個事務進行提交,必須將自動提交設置爲false,然後進行手動提交。一般在我們的項目中,都需要將自動提交設置爲false,即將自動提交關閉,使用手動提交
這個方法中通過對connection實例中的自動提交設置(真或假)進行判斷,如果爲false,表明不執行自動提交,則復位,重新將其設置爲true。(自動提交的默認值爲true)這個操作執行在connection關閉之前。可以看做是連接關閉之前的復位操作。
2.5 問題
在JdbcTransaction中提供的兩個構造器中以Connection爲參數的構造器額作用是什麼呢?
我們需要自動組裝一個完整的Connection,以其爲參數來生產一個事務實例。這用在什麼場景中呢?