MyBatis源码解析(三)——Transaction事务模块

原创做品,能够转载,可是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634151.htmlhtml

一、回顾

  以前介绍了Environment环境类,这实际上是一个单例类,在MyBatis运行开启后只会存在一个惟一的环境实例,虽然咱们能够在Configuration配置文件中配置多个环境,可是项目运行中只会存在其中的一个,通常项目会存在开发环境和测试环境、生产环境三大环境,其是否能够设置到配置文件中,在开发时使用开发环境,测试时使用测试环境,正式运营时可使用生产环境。java

  以前还提到Environment类中有三个字段,除了id以外,TransactionFactory和DataSource都是比较复杂的模块,这一次咱们介绍Transaction模块(即事务模块)。spring

二、事务模块

  事务模块位于org.apache.ibatis.transaction包,这个包内的类均是事务相关的类:sql

  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都是接口类。apache

  Transaction是事务接口,其中定义了四个方法:session

      commit()-事务提交mybatis

      rollBack()-事务回滚框架

      close()-关闭数据库链接ide

      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。具体的配置以下所述:

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的少许系统中才会使用到。

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,以其为参数来生产一个事务实例。这用在什么场景中呢?

相关文章
相关标签/搜索