参考:https://www.jianshu.com/p/f5fc14bde8a0

后续测试代码的完整项目:https://files.cnblogs.com/files/hellohello/demo2.rar

后续说的事务注解都是指 import javax.transaction.Transactional;事务注解如果修饰在类上,则等价与作用在这个类的所有方法上,如果仅修饰在函数上,则仅仅作用在这个函数上,对其他函数没有效果。

  1. 只要加了事务注解,不管是加到bean上,还是加到bean中的函数上,spring才会生成一个代理对象与对应的bean,共两个对象。
  2. 注入到容器中的是代理对象,而不是被代理的bean对象。
  3. 代理对象实际上是被代理对象的子类,通过CGLib动态生成的。
  4. 其他bean中注入的虽然是代理对象,但是对于开发人员来说,就跟直接调用实际的bean对象一样,是透明的,因为代理对象内部会调用被代理对象的对应函数。
  5. 代理对象调用被代理对象函数时,只有抛出了uncheck exception(RuntimeException或Error)时【或配置其他异常】,并且抛出异常的函数处于事务注解的作用范围内时,事务才会回滚。

测试1、2、3点:

服务类代码,成员函数上存在事务注解,所以会生成代理对象

@Service
public class ConfSystemService {
    // 用于记录被代理对象的引用
    public static ConfSystemService ins ;

    public ConfSystemService(){
        ins = this;
    }

    @Transactional
    public void saveWithOk(int id){
        // ...
    }
}

测试类中进行测试

@Autowired
ConfSystemService confSystemService;

@Test
public void test3(){
    Assert.isTrue(!confSystemService.equals(ConfSystemService.ins),"判断是否属于同一个对象");
    Assert.isTrue(confSystemService.getClass().getSuperclass().equals(ConfSystemService.class),"判断是否是父类子类关系");
    Assert.isTrue(!ConfSystemService.ins.getClass().getSuperclass().equals(ConfSystemService.class),"判断是否是父类子类关系");
}

如果把服务类中的事务注解去掉,则不会生成代理对象,那上面例子中的静态属性保存的对象,跟其他地方注册的服务对象,就是同一个对象了。

测试4、5点:

在服务类中添加如下代码:

/**
 * dao.
 */
@Autowired
ConfSystemRepository confSystemRepository;

/**
 * 使用dao保存数据后,报错.
 * @param id .
 */
@Transactional
public void saveWithErr(int id){
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new RuntimeException("模拟错误");
}

/**
 * 正常保存数据.
 * @param id .
 */
@Transactional
public void saveWithOk(int id){
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
}

/**
 * 判断数据是否成功插入到了数据库中.
 * @param id .
 * @return .
 */
public boolean checkExist(int id){
    return confSystemRepository.findById(id).isPresent();
}

执行单元测试

@Test
public void test1() {
    try{
        confSystemService.saveWithErr(888);
    }catch (Exception e){
        e.printStackTrace();
    }
    Assert.isTrue(!confSystemService.checkExist(888));
}

@Test
public void test2() {
    confSystemService.saveWithOk(999);
    Assert.isTrue(confSystemService.checkExist(999));
}

第一个单元测试中,模拟了一个报错,事务就回滚了,所以检查出数据不存在。其中实际调用了代理类的saveWithErr方法,而这个方法中调用了被代理对象的saveWithErr方法,而这个方法中抛出了一个RuntimeException,这个异常被代理类的saveWithErr方法检测到,而且代理类发现当前方法处于事务注解的作用下,所以代理类就会将事务回滚,最后再将这个RuntimeException抛出去,让调用者知道这个方法报错了。

事务没有回滚

以上提到了回滚的两个必要条件:

  1. 当前方法处于事务注解的作用范围内
  2. 方法得抛出RuntimeException或Error的子异常,或配置的指定异常

测试第1条,场景:服务类中未出于事务注解作用下的方法调用了,处于事务注解作用下的方法。服务类中添加如下代码:

public void funcWithNoTrans(int id){
    saveWithErr(id);
}

测试类中添加如下测试用例(和之前的test1很类似,至不要调用的服务方法不一样):

@Test
public void test5() {
    try{
        confSystemService.funcWithNoTrans(888);
    }catch (Exception e){
        e.printStackTrace();
    }
    Assert.isTrue(!confSystemService.checkExist(888));
}

这个测试用例无法通过。是因为代理类执行当前方法时,虽然执行的被代理对象的方法中抛出了异常,但是代理类发现当前方法(funcWithNoTrans)并不是处于事务注解作用下,所以事务没有回滚。

测试第2条,场景:没有抛出uncheck exception,而是抛出了自定义的异常,同时没有做对应配置,在服务类中添加代码:

// 自定义异常
public static class MyException extends Exception {
    // ...
}

// 没有配置
@Transactional
public void saveWithCustomException(int id) throws MyException {
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new MyException();
}

// 有配置
@Transactional(rollbackOn = MyException.class)
public void saveWithCustomCfgException(int id) throws MyException {
    ConfSystem confSystem = new ConfSystem();
    confSystem.setConfigurename("123");
    confSystem.setConfigurevalue("456");
    confSystem.setConfsystemid(id);

    confSystemRepository.save(confSystem);
    throw new MyException();
}

测试代码:

    @Test
    public void test6() {
        try{
            confSystemService.saveWithCustomCfgException(111);
        }catch (Exception e){
            e.printStackTrace();
        }
        Assert.isTrue(!confSystemService.checkExist(111),"抛出已配置的自定义异常");
    }

    @Test
    public void test7() {
        try{
            confSystemService.saveWithCustomException(222);
        }catch (Exception e){
            e.printStackTrace();
        }
        Assert.isTrue(confSystemService.checkExist(222),"抛出未配置的自定义异常");
    }

两个测试都能通过。

还有另外一个场景:服务类内部出现异常了,但是内部try...catch处理掉了,导致代理类检测不出来被代理对象内部其实出现了异常,最终事务也没有回滚。

ps:测试发现,换用另一个spring包下的Transactional注解,具有以上相同的效果,只不过配置注解那里,得改成 rollbackFor。这两个注解功能上其实没啥区别,只是配置的属性名有点差异而已。

其他

  被注解修饰的函数是运行在一个事务内,所以要保证这个函数运行的时间要尽可能短(如不要穿插网络请求,实在不行的话,则将网络请求剥离到这个事务方法之外,这样就不影响这个方法的执行时间了)。而且在这个函数内,该抛的运行时异常要抛出来,而不要trycatch住,否则导致事务无法回滚

  事务注解,加到service方法上,service方法内调用不同的dao来操作数据。一个sevice方法内有两个数据库修改操作以上,才需要在这个service方法上加事务注解

SpringBoot显式事务的更多相关文章

  1. SQL Server显式事务与隐式事务

    事务是单个的工作单元.如果某一事务成功,则在该事务中进行的所有数据修改均会提交,成为数据库中的永久组成部分.如果事务遇到错误且必须取消或回滚,则所有数据库修改均被清除. SQL Server中有一下几 ...

  2. SQLServer之创建显式事务

    显式事务定义 显式事务以 BEGIN TRANSACTION 语句开始,并以 COMMIT 或 ROLLBACK 语句结束. 备注 BEGIN TRANSACTION 使 @@TRANCOUNT 按 ...

  3. 基于 Transaction 类的分布式显式事务

    自.NET2.0以来增加了System.Transactions命名空间,为.NET应用程序带来了一个新的事务编程模型. 这个命名空间提供了几个依赖的TransactionXXX类.Transacti ...

  4. (4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务)

    (4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务) 1.概念:隐式事务,显式事务,自动提交事务 2.操作:如何设置事务模式 3.存储过程中的事务 XACT_ABORT 1 ...

  5. SQL Server Insert时开启显式事务

    如果没法避免一条一条的写入,那么在处理前显示开启一个事务 begin tran  在处理完成后 commit 这样也要比不开显示事务会快很多! while i < 10000begin inse ...

  6. CALayer的隐式动画和显式动画

    隐式事务 任何对于CALayer属性的修改,都是隐式事务,都会有动画效果.这样的事务会在run-loop中被提交. - (void)viewDidLoad { //初始化一个layer,添加到主视图 ...

  7. springBoot service层 事务控制

    springBoot使用事物比较简单,在Application启动类s上添加@EnableTransactionManagement注解,然后在service层的方法上添加@Transactional ...

  8. 当 IDENTITY_INSERT 设置为 OFF 时,不能为表‘XXX’中的标识列插入显式值。

    在创建事务复制时,很多时候不一定使用快照进行初始化,而是使用备份还原初始化.当对有标识列(即identity的自增列)的表进行复制的时候,使用备份还原初始化搭建起来的复制常常就会报错,即:当 IDEN ...

  9. CoreAnimation4-隐式动画和显式动画

    事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在. 当你改变CA ...

随机推荐

  1. Windows应用程序未响应

    昨天在安装postgresql的扩展功能postgis的时候,stackbuilder刚打开就死掉,一直未响应,刚开始以为是内存的原因,后来发现并没有占用太多内存,最后打开vpn发现就可以了,原来是网 ...

  2. UVALive - 6436

    题目链接:https://vjudge.net/contest/241341#problem/C Tree Land Kingdom is a prosperous and lively kingdo ...

  3. 《从0到1学习Flink》—— Flink Data transformation(转换)

    前言 在第一篇介绍 Flink 的文章 <<从0到1学习Flink>-- Apache Flink 介绍> 中就说过 Flink 程序的结构 Flink 应用程序结构就是如上图 ...

  4. C 碎片二 数据类型

    一.概述 C 语言包含的数据类型如下图所示: 二.各种数据类型介绍 2.1 整型 整形包括短整型.整形和长整形. 2.1.1 短整形 short a=1; 2.1.2 整形 一般占4个字节(32位), ...

  5. swift第一课快速体验playground

    最近听说苹果要大力推行swift语言,所以我必须要赶快好好学一学,今天做第一个就遇到问题. 在Xcode7.2欢迎界面,选中创建第一个,我们一般都是默认创建第二个. 创建完后,出现问题了,提示如下: ...

  6. 一个关于laravel部署的讲座

    https://pusher.com/sessions/meetup/laravel-nigeria/deploying-your-laravel-application

  7. Linux查找文件内容(grep)

    转载链接:http://www.eguidedog.net/linux-tutorial/05-grep.php grep是Linux命令行下常用于查找过滤文本文件内容的命令.最简单的用法是: gre ...

  8. enable orgmode latex preview to support retina on mac

    Table of Contents 1. enable orgmode latex preview to support retina on mac 1.1. get the proper versi ...

  9. JDBC连接数据库(Servlet+JSP)

    JDBC(Java Database connectivity),是连接数据库的一种方式.后面的框架Mybatis和Hibernate等都封装的是JDBC.在JDBC中常用的API有4个:Driver ...

  10. String对象中常用的方法有哪些?

    1.length()字符串长度 String str="abc"; System.out.println(str.length()); //输出3 2.charAt()截取一个字符 ...