MyBatis集成Spring事务管理研究

苏友朋

发布于 2019.04.11 09:51 阅读 3545 评论 0

 

MyBatis集成Spring事务管理研究

 

本次研究总结包括两种事务管理方式:

第一种是编程式的事务管理(但是仍然需要使用spring配置,因此不能算是纯编程式(就是使用SqlSession进行提交回滚))。

第二种是注解式的事务管理(使用@Transactional标记事务管理的方法,并使用异常事件来控制提交回滚)。

本次研究总结使用的事务管理类:

org.springframework.jdbc.datasource.DataSourceTransactionManager

 

 

 

一 准备

Maven+Springmvc+mybatis项目

项目如何创建,本次不进行表述,这里只介绍关键配置

数据库表

CREATE TABLE `transaction`  (
  `transaction_id` bigint(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `value_one` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `value_two` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `value_three` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `value_fore` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `value_five` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
  `update_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`transaction_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

Spring+mybatis配置文件(事务相关部分)

<!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 事务注解驱动,标注@Transactional的类和方法将具有事务性 -->
    <tx:annotation-driven/>

实体类(注意,篇幅关系,get,set等基础方法没有列出)

public class Transaction {
    private Long transactionId;

    private String valueOne;

    private String valueTwo;

    private String valueThree;

    private String valueFore;

    private String valueFive;

    private Date createDate;

    private Date updateDate;
}

Mybatis数据库操作接口以及xml(都是mybatis自动生成功能生成出来的代码,可以忽略,这里只展示使用到的接口方法

public interface TransactionMapper {
    int insertSelective(Transaction record);
}
public interface ExtTransactionMapper extends TransactionMapper {
}

意,上面之所以将原生(就是mybatis自动生成功能生成的代码)的mapper类继承一次的原因,因为在开发过程中,我们总会遇到以下两种情况:1、因系统业务需要,原生的DML操作不能满足需求。2、因系统业务需要,数据库表结构需要变更(DDL操作),并且需要重新生成mybatis的代码。

当我们遇到这两种情况的时候,如果都对原生的代码进行操作,就会出现互相覆盖的情况,导致辛苦码的代码丢失。因此使用继承的方式进行代码扩展,可以避免上述情况。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jtexplorer.mapper.TransactionMapper" >
  <insert id="insertSelective" parameterType="com.jtexplorer.entity.Transaction" >
    <selectKey resultType="java.lang.Long" keyProperty="transactionId" order="AFTER" >
      SELECT LAST_INSERT_ID()
    </selectKey>
    insert into transaction
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="valueOne != null" >
        value_one,
      </if>
      <if test="valueTwo != null" >
        value_two,
      </if>
      <if test="valueThree != null" >
        value_three,
      </if>
      <if test="valueFore != null" >
        value_fore,
      </if>
      <if test="valueFive != null" >
        value_five,
      </if>
      <if test="createDate != null" >
        create_date,
      </if>
      <if test="updateDate != null" >
        update_date,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="valueOne != null" >
        #{valueOne,jdbcType=VARCHAR},
      </if>
      <if test="valueTwo != null" >
        #{valueTwo,jdbcType=VARCHAR},
      </if>
      <if test="valueThree != null" >
        #{valueThree,jdbcType=VARCHAR},
      </if>
      <if test="valueFore != null" >
        #{valueFore,jdbcType=VARCHAR},
      </if>
      <if test="valueFive != null" >
        #{valueFive,jdbcType=VARCHAR},
      </if>
      <if test="createDate != null" >
        #{createDate,jdbcType=TIMESTAMP},
      </if>
      <if test="updateDate != null" >
        #{updateDate,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jtexplorer.mapper.ExtTransactionMapper" >
</mapper>

service接口

public interface TransactionService {
    int insertSelective(Transaction record);

    int insertTransactionTest(Transaction record);

    int insertTransactionTestOne(Transaction record,Integer rORc);
}

二 编程式事务管理

事务管理封装的工具类

@Service
public class TransactionalUtils {
    @Resource(name="transactionManager")
    private  DataSourceTransactionManager transactionManager;
    /**
     * 获取事务定义
     */
    private  DefaultTransactionDefinition def;
    private  TransactionStatus status;
    
    /**
     * 事务开始
     */
    public  void start(){
        def= new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        status = transactionManager.getTransaction(def);
    }

    /**
     * 事务回滚(不能和事务提交同时使用,要么提交要么回滚)
     */
    public  void rollBack(){
        transactionManager.rollback(status);
    }

    /**
     * 事务提交(不能喝回滚同时使用,要么提交要么回滚)
     */
    public  void commit(){
        transactionManager.commit(status);
    }
}

Controller

@Slf4j
@RestController
@SuppressWarnings("SpringJavaAutowiringInspection")
@RequestMapping(value = "/mini/transaction")
public class TransactionController {
    @Resource
    private TransactionService transactionService;
    @Resource
    TransactionalUtils transactionalUtils;
    @PostMapping(value = "/insertTransactionTest")
    public JsonResult insertTransactionTest(@ModelAttribute Transaction transaction,
                                            @RequestParam(required = false, defaultValue = "1") Integer rORc) {
        JsonResult jsonResult = new JsonResult();
        transactionalUtils.start();
        int n = transactionService.insertTransactionTest(transaction);
        if (rORc == 0) {
            transactionalUtils.rollBack();
            jsonResult.buildNew(false, n, null, null, null);
        } else {
            transactionalUtils.commit();
            jsonResult.buildNew(true, n, null, null, null);
        }
        transactionalUtils.start();
        transaction.setValueOne("11111111");
        transactionService.insertTransactionTestOne(transaction, rORc);
        return jsonResult;
    }
}

Service接口的实现类中本次使用的方法

    @Override
    public int insertTransactionTest(Transaction record) {
        int n = insertSelective(record);
        return n;
    }

    @Override
    public int insertTransactionTestOne (Transaction record,Integer rORc) {
       int n = insertSelective(record);
        if (rORc == 0 ) {
            transactionalUtils.rollBack();
        } else {
            transactionalUtils.commit();
        }
        return n;    
}

准备工作完成之后,开启项目,并使用postMan(也可以使用其他类似的工具)请求该接口:

本次测试使用的,看代码的逻辑是这样的:rORc为0的时候,两次插入操作都被回滚。

以下是运行结果:

控制台输出的运行结果:

2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.transaction.SpringManagedTransaction  - JDBC Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] will be managed by Spring
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==>  Preparing: insert into transaction ( value_one, value_two, value_three, value_fore, value_five ) values ( ?, ?, ?, ?, ? ) 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==> Parameters: 1(String), 2(String), 3(String), 4(String), 5(String)
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - <==    Updates: 1
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==>  Preparing: SELECT LAST_INSERT_ID() 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==> Parameters: 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - <==      Total: 1
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.SqlSessionUtils  - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6906cb13]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.SqlSessionUtils  - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6906cb13]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.SqlSessionUtils  - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6906cb13]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Initiating transaction rollback
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Rolling back JDBC transaction on Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Releasing JDBC Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] after transaction
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceUtils  - Returning JDBC Connection to DataSource
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Creating new transaction with name [null]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Acquired Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] for JDBC transaction
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.springframework.jdbc.datasource.DataSourceTransactionManager  - Switching JDBC Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] to manual commit
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.SqlSessionUtils  - Creating a new SqlSession
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.SqlSessionUtils  - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b1325cf]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] org.mybatis.spring.transaction.SpringManagedTransaction  - JDBC Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] will be managed by Spring
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==>  Preparing: insert into transaction ( value_one, value_two, value_three, value_fore, value_five ) values ( ?, ?, ?, ?, ? ) 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==> Parameters: 11111111(String), 2(String), 3(String), 4(String), 5(String)
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective  - <==    Updates: 1
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==>  Preparing: SELECT LAST_INSERT_ID() 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==> Parameters: 
2019-04-10 16:49:40 DEBUG [http-nio-8080-exec-1] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - <==      Total: 1

结果表明两次运行的结果都是正确的。也就是说两次运行都成功插入了数据。那么我们看一下数据库中,表里是否出现了新的数据(数据库查看软件:navicat)

综合以上两个结果,我们可以得出结论:数据库确实成功插入了数据,但是没有提交,两次事务都被回滚了。

接下来我们测试提交的情况:

查看数据库:

 

上述过程是否是两个事务,是否能够独立进行提交和回滚操作,我已经测试过了,测试方案就是修改回滚和提交的条件,让两次插入的提交判断条件不相同,就可以了。此处不再详述

三 注解式事务管理

这个注解用于开启事物管理,注意@Transactional注解的使用前提是该方法所在的类是一个Spring Bean,因此(1)中的@Service注解是必须的。换句话说,假如你给方法加了@Transactional注解却没有给类加@Service、@Repository、@Controller、@Component四个注解其中之一将类声明为一个Spring的Bean,那么对方法的事物管理,是不会起作用的。

@Transactional注解,它可以精细到具体的类甚至具体的方法上(区别是同一个类,对方法的事物管理配置会覆盖对类的事务管理配置),另外,声明式事物中的一些属性,在@Transaction注解中都可以进行配置,下面总结一下常用的一些属性

(1) @Transactional(propagation = Propagation.REQUIRED)

 

最重要的先说,propagation属性表示的是事物的传播特性,一共有以下几种:

事物传播特性  

  作      用

Propagation.REQUIRED  

  方法运行时如果已经处在一个事物中,那么就加入到这个事物中,否则自己新建一个事物,REQUIRED是默认的事物传播特性

Propagation.NOT_SUPPORTED  

如果方法没有关联到一个事物,容器不会为它开启一个事物,如果方法在一个事物中被调用,该事物会被挂起直到方法调用结束再继续执行

Propagation.REQUIRES_NEW   

不管是否存在事物,该方法总会为自己发起一个新的事物,如果方法已经运行在一个事物中,则原有事物挂起,新的事物被创建

Propagation.MANDATORY  

该方法只能在一个已经存在的事物中执行,业务方法不能发起自己的事物,如果在没有事物的环境下被调用,容器抛出异常

Propagation.SUPPORTS   

该方法在某个事物范围内被调用,则方法成为该事物的一部分,如果方法在该事物范围内被调用,该方法就在没有事物的环境下执行

Propagation.NEVER  

该方法绝对不能在事物范围内执行,如果在就抛出异常,只有该方法没有关联到任何事物,才正常执行

Propagation.NESTED 

如果一个活动的事物存在,则运行在一个嵌套的事物中。如果没有活动事物,则按REQUIRED属性执行,它只对DataSourceTransactionManager事物管理器有效

 

由于没有指定propagation属性,因此事物传播特性为默认的REQUIRED

(2)@Transactional(isolation = Isolation.DEFAULT)

事物隔离级别,这个不细说了,可以参看事物及事物隔离级别一文。

(3)@Transactional(readOnly = true)

该事物是否为一个只读事物,配置这个属性可以提高方法执行效率。

(4)@Transactional(rollbackFor = {ArrayIndexOutOfBoundsException.class, NullPointerException.class})

遇到方法抛出ArrayIndexOutOfBoundsException、NullPointerException两种异常会回滚数据,仅支持RuntimeException的子类。

(5)@Transactional(noRollbackFor = {ArrayIndexOutOfBoundsException.class, NullPointerException.class})

 

这个和上面的相反,遇到ArrayIndexOutOfBoundsException、NullPointerException两种异常不会回滚数据,同样也是仅支持RuntimeException的子类。

(6)@Transactional(rollbackForClassName = {"NullPointerException"})、@Transactional(noRollbackForClassName = {"NullPointerException"})

这两个放在一起说了,和上面的(4)、(5)差不多,无非是(4)、(5)是通过.class来指定要回滚和不要回滚的异常,这里是通过字符串形式的名字来制定要回滚和不要回滚的异常。

(7)@Transactional(timeout = 30)

事物超时时间,单位为秒。

(8)@Transactional(value = "tran_1")

value这个属性主要就是给某个事物一个名字而已,这样在别的地方就可以使用这个事物的配置。

 

知识点介绍完毕,接下来介绍我们的试验

首先是controller接口:

/**
     * 注解式事务管理
     *
     * @return JsonResult
     */
    @MethodLog(remark = "注解式事务管理")
    @PostMapping(value = "/annotationTransactional")
    public JsonResult annotationTransactional(@ModelAttribute Transaction transaction,
                                              @RequestParam(required = false, defaultValue = "1") Integer rORc) {
        JsonResult jsonResult = new JsonResult();
        try {
            int n = transactionService.insertTransactionTestOne(transaction, rORc);
            if (n > 0) {
                jsonResult.setSuccess(true);
            }
        } catch (Exception e) {
            jsonResult.setSuccess(false);
            jsonResult.setFailReason(e.toString());
        }
        return jsonResult;
    }

Service接口的实现类中本次使用的方法:

@Override
    @Transactional
    public int insertTransactionTestOne (Transaction record,Integer rORc) {
        insertSelective(record);
        int n = 1/rORc;
        return n;
    }

请求该接口

查看数据库:

接下来,我们测试回滚:

首先查询控制台输出:

2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] org.mybatis.spring.transaction.SpringManagedTransaction  - JDBC Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J] will be managed by Spring
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==>  Preparing: insert into transaction ( value_one, value_two, value_three, value_fore, value_five ) values ( ?, ?, ?, ?, ? ) 
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective  - ==> Parameters: 1(String), 2(String), 3(String), 4(String), 5(String)
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective  - <==    Updates: 1
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ooo Using Connection [jdbc:mysql://192.168.123.199:3306/shopping_basket?useSSL=false&serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true, UserName=root@desktop-13jt1e1, MySQL Connector/J]
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==>  Preparing: SELECT LAST_INSERT_ID() 
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - ==> Parameters: 
2019-04-10 17:16:26 DEBUG [http-nio-8080-exec-5] com.jtexplorer.mapper.TransactionMapper.insertSelective!selectKey  - <==      Total: 1

然后查看数据库结果:

数据库中只有第一次使用测试插入的数据,但是控制台信息显示确实有一条数据插入成功了。因此我们可以得出结论,事务已经回滚了。

 

 

 

四 研究过程总结

两种方式的实现使用过程在上文实验中已经介绍的很清楚。

 

编程式事务管理试验,得出的结论如下:

优点:

  1. 无需使用异常进行回滚管理,可以在程序中任意进行事务分隔,更容易进行需要和不需要事务管理的逻辑的分隔。
  2. 事务一直在方法个进行传递,上层调用方法中开启的事务,是可以在下层被调用方法中进行提交或回滚的。

缺点:

  1. 由于需要手动进行提交或回滚操作,所以很容易出现某个程序分支忘记提交或回滚操作,导致事务一直无法得到关闭。或者很容易出现多次提交或回滚操作(注意,两次中间没有进行提交或回滚操作的start操作,会合并成一个事务,程序中两次进行提交或回滚操作时)。一旦出现如上情况极有可能对系统的正常运行导致不可预估的影响(该问题,如果事务内的程序出现异常导致程序运行中断,同样会引发该问题)。
  2. Mybatis的一级缓存机制与这种事务管理方式结合时,很有可能造成缓存无法被清理的问题。

 

注解式事务管理试验,得出的结论如下:

优点:

  1. 提交或回滚操作不需要我们手动显式的操作,避免了编程式事务管理的缺点

缺点:

  1. 重叠的注解会被合并,但是事务最终提交或回滚是在最外层的注解的方法执行结束或出现异常之后。就是说事务管理方便,但是不够灵活。
  2. 需要使用异常进行控制回滚操作(人工异常,比如1/0),该操作不能捕获,但是在其上级方法(本次实验可以看到,在controller中是可以使用try catch捕获异常的)可以捕获该异常。

 

 

两种方式在线程层面上的区别目前还没有进行研究。