之前介绍了 Environment 环境类,这其实是一个单例类,在 MyBatis 运行开启后只会存在一个唯一的环境实例,虽然我们可以在 Configuration 配置文件中配置多个环境,但是项目运行中只会存在其中的一个,一般项目会存在开发环境和测试环境、生产环境三大环境,其是否可以设置到配置文件中,在开发时使用开发环境,测试时使用测试环境,正式运营时可以使用生产环境。
之前还提到 Environment 类中有三个字段,除了 id 之外,TransactionFactory 和 DataSource 都是比较复杂的模块,这一次我们介绍 Transaction 模块(即事务模块)。
事务模块位于 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 的事务模块采用的是工厂模式。
位于 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 与其一样,是对事务工厂的抽象,同样便于具体类型的事务工厂的扩展实现。
说到这里,就不得不提到 MyBatis 里内置的两种事务类型及对应的事务工厂了,还记得在上一文中给出的 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 类的无参构造器中定义的,这里拿来仅用于展示,具体说明以后会介绍。
这里提一句:类型别名注册器额原理就是将别名与具体的类类型以键值对的方式保存到一个 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 的少量系统中才会使用到。
虽然 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 实例,然后我们就可以使用这个实例来进行事务操作:提交、回滚、关闭。
在之前的代码中我们能看到在关闭操作之前调用了一个方法: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 关闭之前。可以看做是连接关闭之前的复位操作。
在 JdbcTransaction 中提供的两个构造器中以 Connection 为参数的构造器额作用是什么呢?
我们需要自动组装一个完整的 Connection,以其为参数来生产一个事务实例。这用在什么场景中呢?
来源: http://www.cnblogs.com/V1haoge/p/6634151.html