上一篇博客我們介紹了mybatis中關於數據源的配置原理,本篇博客介紹mybatis的事務管理。
對於事務,我們是在mybatis-configuration.xml 文件中配置的:
關於解析 <environments />標簽在上一篇數據源的配置我們已經介紹了,不了解的可以參考上篇博客。
1、mybatis 支持的事務類圖
mybatis 支持的所有事務的所有類都在如下包中:
下面通過類圖來理解該包中所有類的關系:
2、mybatis 支持的兩種事務類型管理器
通過配置文件中的 type 屬性:
<transactionManager type="JDBC" />
我們可以配置不同的事務管理器來管理事務。在mybatis中支持兩種事務類型管理器,分別是:
①、type = "JDBC":這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
②、type="MANAGED":這個配置幾乎沒做什么。它從來不提交或回滾一個連接,而是讓容器來管理事務的整個生命周期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接,然而一些容器並不希望這樣,因此需要將 closeConnection 屬性設置為 false 來阻止它默認的關閉行為。例如:
1 <transactionManager type="MANAGED"> 2 <property name="closeConnection" value="false"/> 3 </transactionManager>
注意:和數據源配置一樣,通常項目中我們不會單獨使用 mybatis 來管理事務。比如選擇框架 Spring +mybatis,這時候沒有必要配置事務管理器, 因為 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。
再回頭看看在 mybatis 的 org.apache.ibatis.transaction 包下的所有類,也就是上面的類圖。mybatis的事務首先會定義一個事務接口 Transaction,
3、初始化事務管理器
我們說事務(Transaction),一般是指要做的或所做的事情。在數據庫中,事務具有如下四個屬性:
①、原子性(atomicity):一個事務是一個不可分割的工作單位,事務中包括的諸操作要么都做,要么都不做。
②、一致性(consistency):事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
③、隔離性(isolation):一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相干擾。
④、持久性(durability):持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
這也就是常說的事務 ACID 特性。而在程序中,對於事務的操作通常是:
1、創建事務(create)
2、提交事務(commit)
3、回滾事務(rollback)
4、關閉事務(close)
在mybatis的 org.apache.ibatis.transaction 包下的 Transaction 接口便為我們定義了這一套操作:

1 /** 2 * Copyright 2009-2016 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.apache.ibatis.transaction; 17 18 import java.sql.Connection; 19 import java.sql.SQLException; 20 21 /** 22 * Wraps a database connection. 23 * Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close. 24 * 25 * @author Clinton Begin 26 */ 27 public interface Transaction { 28 29 /** 30 * Retrieve inner database connection 31 * @return DataBase connection 32 * @throws SQLException 33 */ 34 Connection getConnection() throws SQLException; 35 36 /** 37 * Commit inner database connection. 38 * @throws SQLException 39 */ 40 void commit() throws SQLException; 41 42 /** 43 * Rollback inner database connection. 44 * @throws SQLException 45 */ 46 void rollback() throws SQLException; 47 48 /** 49 * Close inner database connection. 50 * @throws SQLException 51 */ 52 void close() throws SQLException; 53 54 /** 55 * Get transaction timeout if set 56 * @throws SQLException 57 */ 58 Integer getTimeout() throws SQLException; 59 60 }
同時,mybatis為了獲取事務采用了工廠模式,定義了一個工廠接口:TransactionFactory
通過實現該工廠接口,mybatis提供了兩種不同的事務管理器:
這兩種事務管理器的獲取也是采用了工廠模式。下面我們來分別看看這兩種事務管理器。
4、JdbcTransaction
當在配置文件中配置:type = "JDBC"時,就是用 JdbcTransaction 來管理事務。使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
代碼如下:
1 public class JdbcTransaction implements Transaction { 2 3 private static final Log log = LogFactory.getLog(JdbcTransaction.class); 4 //數據庫連接 5 protected Connection connection; 6 //數據源 7 protected DataSource dataSource; 8 //隔離級別 9 protected TransactionIsolationLevel level; 10 //是否自動提交 11 protected boolean autoCommmit; 12 13 public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { 14 dataSource = ds; 15 level = desiredLevel; 16 autoCommmit = desiredAutoCommit; 17 } 18 19 public JdbcTransaction(Connection connection) { 20 this.connection = connection; 21 } 22 23 @Override 24 public Connection getConnection() throws SQLException { 25 if (connection == null) { 26 openConnection(); 27 } 28 return connection; 29 } 30 31 //調用connection.commit()來實現 32 @Override 33 public void commit() throws SQLException { 34 if (connection != null && !connection.getAutoCommit()) { 35 if (log.isDebugEnabled()) { 36 log.debug("Committing JDBC Connection [" + connection + "]"); 37 } 38 connection.commit(); 39 } 40 } 41 42 //調用connection.rollback()來實現 43 @Override 44 public void rollback() throws SQLException { 45 if (connection != null && !connection.getAutoCommit()) { 46 if (log.isDebugEnabled()) { 47 log.debug("Rolling back JDBC Connection [" + connection + "]"); 48 } 49 connection.rollback(); 50 } 51 } 52 53 //調用connection.close()來實現 54 @Override 55 public void close() throws SQLException { 56 if (connection != null) { 57 resetAutoCommit(); 58 if (log.isDebugEnabled()) { 59 log.debug("Closing JDBC Connection [" + connection + "]"); 60 } 61 connection.close(); 62 } 63 } 64 65 protected void setDesiredAutoCommit(boolean desiredAutoCommit) { 66 try { 67 //事務提交狀態不一致 68 if (connection.getAutoCommit() != desiredAutoCommit) { 69 if (log.isDebugEnabled()) { 70 log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); 71 } 72 connection.setAutoCommit(desiredAutoCommit); 73 } 74 } catch (SQLException e) { 75 // Only a very poorly implemented driver would fail here, 76 // and there's not much we can do about that. 77 throw new TransactionException("Error configuring AutoCommit. " 78 + "Your driver may not support getAutoCommit() or setAutoCommit(). " 79 + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); 80 } 81 } 82 83 protected void resetAutoCommit() { 84 try { 85 if (!connection.getAutoCommit()) { 86 // MyBatis does not call commit/rollback on a connection if just selects were performed. 87 // Some databases start transactions with select statements 88 // and they mandate a commit/rollback before closing the connection. 89 // A workaround is setting the autocommit to true before closing the connection. 90 // Sybase throws an exception here. 91 if (log.isDebugEnabled()) { 92 log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); 93 } 94 connection.setAutoCommit(true); 95 } 96 } catch (SQLException e) { 97 if (log.isDebugEnabled()) { 98 log.debug("Error resetting autocommit to true " 99 + "before closing the connection. Cause: " + e); 100 } 101 } 102 } 103 104 protected void openConnection() throws SQLException { 105 if (log.isDebugEnabled()) { 106 log.debug("Opening JDBC Connection"); 107 } 108 connection = dataSource.getConnection(); 109 if (level != null) { 110 connection.setTransactionIsolation(level.getLevel()); 111 } 112 setDesiredAutoCommit(autoCommmit); 113 } 114 115 @Override 116 public Integer getTimeout() throws SQLException { 117 return null; 118 } 119 120 }
5、ManagedTransaction
ManagedTransaction的代碼實現幾乎都是一個空的方法,它選擇讓容器來管理事務的整個生命周期(比如 JEE 應用服務器的上下文)。
1 /** 2 * Copyright 2009-2016 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.apache.ibatis.transaction.managed; 17 18 import java.sql.Connection; 19 import java.sql.SQLException; 20 import javax.sql.DataSource; 21 22 import org.apache.ibatis.logging.Log; 23 import org.apache.ibatis.logging.LogFactory; 24 import org.apache.ibatis.session.TransactionIsolationLevel; 25 import org.apache.ibatis.transaction.Transaction; 26 27 /** 28 * {@link Transaction} that lets the container manage the full lifecycle of the transaction. 29 * Delays connection retrieval until getConnection() is called. 30 * Ignores all commit or rollback requests. 31 * By default, it closes the connection but can be configured not to do it. 32 * 33 * @author Clinton Begin 34 * 35 * @see ManagedTransactionFactory 36 */ 37 public class ManagedTransaction implements Transaction { 38 39 private static final Log log = LogFactory.getLog(ManagedTransaction.class); 40 41 private DataSource dataSource; 42 private TransactionIsolationLevel level; 43 private Connection connection; 44 private boolean closeConnection; 45 46 public ManagedTransaction(Connection connection, boolean closeConnection) { 47 this.connection = connection; 48 this.closeConnection = closeConnection; 49 } 50 51 public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { 52 this.dataSource = ds; 53 this.level = level; 54 this.closeConnection = closeConnection; 55 } 56 57 @Override 58 public Connection getConnection() throws SQLException { 59 if (this.connection == null) { 60 openConnection(); 61 } 62 return this.connection; 63 } 64 65 @Override 66 public void commit() throws SQLException { 67 // Does nothing 68 } 69 70 @Override 71 public void rollback() throws SQLException { 72 // Does nothing 73 } 74 75 @Override 76 public void close() throws SQLException { 77 if (this.closeConnection && this.connection != null) { 78 if (log.isDebugEnabled()) { 79 log.debug("Closing JDBC Connection [" + this.connection + "]"); 80 } 81 this.connection.close(); 82 } 83 } 84 85 protected void openConnection() throws SQLException { 86 if (log.isDebugEnabled()) { 87 log.debug("Opening JDBC Connection"); 88 } 89 this.connection = this.dataSource.getConnection(); 90 if (this.level != null) { 91 this.connection.setTransactionIsolation(this.level.getLevel()); 92 } 93 } 94 95 @Override 96 public Integer getTimeout() throws SQLException { 97 return null; 98 } 99 100 }