MyBatis源码解析之Transaction事务模块

目录
  • 1、回顾
  • 2、事务模块
    • 2.1 事务接口
    • 2.2 MyBatis事务类型
    • 2.3 JDBC事务模型
    • 2.4 关于自动提交
    • 2.5 问题

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()-获取数据库连接

如下代码所示:

package org.apache.ibatis.transaction;
import java.sql.Connection;
import java.sql.SQLException;
/**
 * 事务,包装了一个Connection, 包含commit,rollback,close方法
 * 在 MyBatis 中有两种事务管理器类型(也就是 type=”[JDBC|MANAGED]”):
 */
public interface Transaction {
  Connection getConnection() throws SQLException;
 void commit() throws SQLException;
 void rollback() throws SQLException;
 void close() throws SQLException;
}

TransactionFactory是事务工厂接口,其中定义了三个方法:

setProperties(Properties props)-设置属性

newTransaction(Connection conn)-创建事务实例

newTransaction(DataSource dataSource,TransactionIsolationLevel level,boolean autoCommit)-创建事务实例

如下代码所示:

package org.apache.ibatis.transaction;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.session.TransactionIsolationLevel;
/**
 * 事务工厂
 */
public interface TransactionFactory {
 //设置属性
 void setProperties(Properties props);
 //根据Connection创建Transaction
 Transaction newTransaction(Connection conn);
 //根据数据源和事务隔离级别创建Transaction
 Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

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

其中有四个参数:

 protected Connection connection;
 protected DataSource dataSource;
 protected TransactionIsolationLevel level;
 protected boolean autoCommmit;

  

这四个参数分别对应事务工厂中的两个生产方法中的总共四个参数,对应的在事务类中定义了两个构造器,构造实例的同时进行赋值:

public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
   dataSource = ds;
   level = desiredLevel;
   autoCommmit = desiredAutoCommit;
 }
 public JdbcTransaction(Connection connection) {
   this.connection = connection;
 }

其次在该类中实现了Transaction接口,实现了其中的四个方法,三个功能性方法,和一个获取数据库连接的方法。三个功能方法分别是提交、回滚和关闭。

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

 @Override
 public void rollback() throws SQLException {
   if (connection != null && !connection.getAutoCommit()) {
     if (log.isDebugEnabled()) {
       log.debug("Rolling back JDBC Connection [" + connection + "]");
     }
     connection.rollback();
   }
 }

 @Override
 public void close() throws SQLException {
   if (connection != null) {
     resetAutoCommit();
     if (log.isDebugEnabled()) {
       log.debug("Closing JDBC Connection [" + connection + "]");
     }
     connection.close();
   }
 }

通过观察这三个方法,可以发现,其中都使用了connection来进行具体操作,因此这些方法使用的前提就是先获取connection数据库连接,Connection的获取使用getConnection()方法

 @Override
 public Connection getConnection() throws SQLException {
   if (connection == null) {
     openConnection();
   }
   return connection;
 }

在上面的方法中调用了openConnection()方法:

 protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
 }

可见connection是从数据源dataSource中获取的,最后会调用setDesiredAutoCommit()方法:

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
     // Only a very poorly implemented driver would fail here,
     // and there's not much we can do about that.
     throw new TransactionException("Error configuring AutoCommit.  "
         + "Your driver may not support getAutoCommit() or setAutoCommit(). "
         + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
   }
 }

这个方法的目的就是为connection中的自动提交赋值(真或假)。

这么看来,我们创建事务实例所提供的三个参数就是为connection服务的,其中DataSource是用来获取Connection实例的,而TransactionIsolationLevel(事务级别)和boolean(自动提交)是用来填充connection的,通过三个参数我们获得了一个圆满的Connection实例,然后我们就可以使用这个实例来进行事务操作:提交、回滚、关闭。

2.4 关于自动提交

在之前的代码中我们能看到在关闭操作之前调用了一个方法:resetAutoCommit():

 protected void resetAutoCommit() {
   try {
     if (!connection.getAutoCommit()) {
       // MyBatis does not call commit/rollback on a connection if just selects were performed.
       // Some databases start transactions with select statements
       // and they mandate a commit/rollback before closing the connection.
       // A workaround is setting the autocommit to true before closing the connection.
       // Sybase throws an exception here.
       if (log.isDebugEnabled()) {
        log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
      }
      connection.setAutoCommit(true);
    }
  } catch (SQLException e) {
    log.debug("Error resetting autocommit to true "
        + "before closing the connection.  Cause: " + e);
  }
}

这里相对自动提交做个解说,如果设置自动提交为真,那么数据库将会将每一个SQL语句当做一个事务来执行,为了将多条SQL当做一个事务进行提交,必须将自动提交设置为false,然后进行手动提交。一般在我们的项目中,都需要将自动提交设置为false,即将自动提交关闭,使用手动提交

这个方法中通过对connection实例中的自动提交设置(真或假)进行判断,如果为false,表明不执行自动提交,则复位,重新将其设置为true。(自动提交的默认值为true)这个操作执行在connection关闭之前。可以看做是连接关闭之前的复位操作。

2.5 问题

在JdbcTransaction中提供的两个构造器中以Connection为参数的构造器额作用是什么呢?

我们需要自动组装一个完整的Connection,以其为参数来生产一个事务实例。这用在什么场景中呢?

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 解析Mybatis判断表达式源码分析

    在我们开发过程中用 Mybatis 经常会用到下面的例子 Mapper如下 Map<String ,String > testArray(@Param("array") String [] array); XMl中的sql如下 <select id="testArray" resultType="map"> select * from t_ams_ac_pmt_dtl where cpt_pro=#{cptProp} &l

  • 通过源代码分析Mybatis的功能流程详解

    SQL解析 Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种. DynamicSqlSource,动态SQL,获取SQL(getBoundSQL方法中)的时候生成参数化SQL. RawSqlSource,原始SQL,创建对象时直接生成参数化SQL. 因为RawSqlSource不会重复去生成参数化SQL,调用的时候直接传入参数并执行,而DynamicSqlSource则是每次执行的时候参数化SQL,所以RawSqlSourc

  • mybatis中的扩展实现源码解析

    前言 最近项目中需要用到mybatis的扩展,就深入看了下mybatis的实现,对其灵活性和扩展性的设计思想还是非常佩服的 首先说一下mybatis的拦截器使用方法:继承其Intercepter接口,实现org.apache.ibatis.plugin.Interceptor#intercept方法,在其中或者对其要执行的方法进行拦截,或者对返回值进行解析 同时基于org.apache.ibatis.plugin.Intercepts和org.apache.ibatis.plugin.Signa

  • mybatis开启spring事务代码解析

    1.事务 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.最终都是调用数据库连接来完成事务的开启.提交和回滚. 2.模块 那么在对于spring事务而言,几个不可或缺的模块就是数据源.事务管理器以及事务编程 3.xml配置 <!--事务管理器--> <bean id="springTransactionManager" class="org.springframework.jdbc.datasourc

  • MyBatis源码解析之Transaction事务模块

    目录 1.回顾 2.事务模块 2.1 事务接口 2.2 MyBatis事务类型 2.3 JDBC事务模型 2.4 关于自动提交 2.5 问题 1.回顾 之前介绍了Environment环境类,这其实是一个单例类,在MyBatis运行开启后只会存在一个唯一的环境实例,虽然我们可以在Configuration配置文件中配置多个环境,但是项目运行中只会存在其中的一个,一般项目会存在开发环境和测试环境.生产环境三大环境,其是否可以设置到配置文件中,在开发时使用开发环境,测试时使用测试环境,正式运营时可以

  • Mybatis源码解析之事务管理

    目录 Mybatis事务管理 和Spring整合后的事务管理 Mybatis事务管理 我们可以在mybatis-config.xml中配置事务管理器的实现 <transactionManager type="JDBC"/> 当值为JDBC时,事务管理实现类为JdbcTransaction,底层利用数据库的Connection来管理事务 当值为MANAGED时,事务管理实现类为ManagedTransactionFactory,但它对事务的管理是一个空实现,将事务管理交给外部

  • MyBatis源码解析——获取SqlSessionFactory方式

    目录 MyBatis源码解析_获取SqlSessionFactory 首先从Resources.getResourceAsReader(path) 进入到SqlSessionFactoryBuilder.build(Reader)方法中 进入到mapperElement(XNode)方法后 如果子节点名字不是package 经过一系列的解析,终于完成了 用MyBatis的配置文件获取SqlSessionFactory MyBatis源码解析_获取SqlSessionFactory 我们都知道,在

  • 【MyBatis源码全面解析】MyBatis一二级缓存介绍

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率. MyBatis的缓存分为两种: 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库 二级缓存,二级缓存是Mapper

  • Spring AOP实现声明式事务机制源码解析

    目录 一.声明式全局事务 二.源码 三.小结: 一.声明式全局事务 在Seata示例工程中,能看到@GlobalTransactional,如下方法示例: @GlobalTransactional public boolean purchase(long accountId, long stockId, long quantity) { String xid = RootContext.getXID(); LOGGER.info("New Transaction Begins: " +

  • Spring源码解析之事务传播特性

    一.使用方式 可以采用Transactional,配置propagation即可. 打开org.springframework.transaction.annotation.Transactional可见默认传播特性是REQUIRED. /** * The transaction propagation type. * <p>Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.inte

  • Spring源码解析之编程式事务

    一.前言 在Spring中,事务有两种实现方式: 编程式事务管理: 编程式事务管理使用TransactionTemplate可实现更细粒度的事务控制.声明式事务管理: 基于Spring AOP实现.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单(尤其是配合spring boot自动配置,可以说是精简至极!),且大部分业务都可

  • Java Spring AOP源码解析之事务实现原理

    目录 不用Spring管理事务? 编程式事务管理 使用PlatformTransactionManager 使用TransactionTemplate 声明式事务管理 使用@Transactional注解 源码解析 参考博客 总结 不用Spring管理事务? 让我们先来看一下不用spring管理事务时,各种框架是如何管理事务的 使用JDBC来管理事务 使用Hibernate来管理事务 业务逻辑和事务代码是耦合到一块的,并且和框架的具体api绑定了.当我们换一种框架来实现时,里面对事务控制的代码就

  • Android10 客户端事务管理ClientLifecycleManager源码解析

    目录 正文 ClientLifecycleManager ClientTransaction TransactionExecutor executeLifecycleState 正文 在Android 10 App启动分析之Activity启动篇(二)一文中,简单地介绍了Activity的生命周期管理器是如何调度Activity进入onCreate生命周期的流程.这篇文章,我们将详细地分析framework中activity的生命周期管理功能,从更宏观的角度来更全面地了解生命周期及相关事务的工作

  • Python中getpass模块无回显输入源码解析

    本文主要讨论了python中getpass模块的相关内容,具体如下. getpass模块 昨天跟学弟吹牛b安利Python标准库官方文档的时候偶然发现了这个模块.仔细一看内容挺少的,只有两个主要api,就花了点时间阅读了一下源码,感觉挺实用的,在这安利给大家. getpass.getpass(prompt='Password: ', stream=None) 调用该函数可以在命令行窗口里面无回显输入密码.参数prompt代表提示字符串,默认是'Password: '.在Unix系统中,strea

随机推荐