十四、事物配置
对于事务,我们是在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 }