Spring的@Transactional嵌套解读

 更新时间:2024年11月08日 15:18:03   作者:Lanje Wang  
本文通过详细测试,探究了Spring的@Transactional注解在事务嵌套和局部回滚场景下的行为表现,文中通过多个测试案例,阐述了不同传播行为(如REQUIRES_NEW)对事务嵌套处理的影响,以及rollbackFor属性在异常管理中的作用和限制,通过实验总结

事务嵌套局部回滚的问题,很是费解。

本文将做一个详细的测试,加强对Spring的@Transactional 理解和使用

1、两个单独不干扰事务

	@RequestMapping("/test")
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(null);
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }
    @Override
    @Transactional
    public void test1(LoveFile loveFile) {
        mapper.insertSelective(loveFile);

    }
    @Override
    @Transactional
    public void test2(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }

由于我给第二个test2插入主键为空,报错。因此库里只有一条

总结:两个单独事务互不干扰。错了就是错了,很好理解

2、普通嵌套事务

	@RequestMapping("/test")
    @Transactional
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(null);
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }

子事务不变,删除刚才的数据后,测试。

库里空空。

总结:加在外层的事务起了作用,在test2报错时回滚了test1

3、嵌套事务、三个子事务中间一个加(propagation = Propagation.REQUIRES_NEW)

Propagation取值:

  • REQUIRED(默认值):在有transaction状态下执行;如当前没有transaction,则创建新的transaction;
  • SUPPORTS:如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行;
  • MANDATORY:必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException;
  • REQUIRES_NEW:创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起;
  • NOT_SUPPORTED:在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起;
  • NEVER:在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。

本文重点研究 REQUIRES_NEW

@RequestMapping("/test")
    @Transactional
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test3 = new LoveFile();
        test3.setFileUuid(get32Uuid());
        test3.setFileName("test3");
        loveFileService.test3(test3);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(null);
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }
@Override
    @Transactional
    public void test1(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional
    public void test2(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test3(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }

REQUIRES_NEW官方文档解释:

Create a new transaction, and suspend the current transaction if one exists.

意思是,创建一个新事务,如果当前存在事务,将这个事务挂起。

也就是说test3,是独立于外层事务的单独的事务,他已经挂起的外层事务,他要把自己的问题处理完再去管别人。

因此结果是

总结:不给子事务加(propagation = Propagation.REQUIRES_NEW),相当于加入了外层事务。加了则当做新的子事务。

test2报错了,影响了test1 ,却不能影响test3,已经说明了此问题

4、嵌套事务、三个子事务中间一个加(propagation = Propagation.REQUIRES_NEW),偏偏出错了

	@RequestMapping("/test")
    @Transactional
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test3 = new LoveFile();
        test3.setFileUuid(null);
        test3.setFileName("test3");
        loveFileService.test3(test3);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(get32Uuid());
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }

我把test3主键为空,因此他会有SQLException。来研究下,他会不会影响整体。

总结: 虽然他是开辟的新事物,但是出错了,还是会牵连整体的

至此,(propagation = Propagation.REQUIRES_NEW) 已经解释清楚了。

我觉得子事务,不加这个东西的话不叫子事务。不加的话直接加入了外层事务。那还要个单单 @Transactional何用?

建议大家对子事务都加上这条件。

rollbackFor官方文档解释:

在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚

5、rollbackFor了一个异常,却会报另外异常

@RequestMapping("/test")
    @Transactional
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test3 = new LoveFile();
        test3.setFileUuid(null);
        test3.setFileName("test3");
        loveFileService.test3(test3);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(get32Uuid());
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }
 @Override
    @Transactional
    public void test1(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional
    public void test2(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional(rollbackFor=SQLException.class,propagation = Propagation.REQUIRES_NEW)
    public void test3(LoveFile loveFile) {
        int  a = 1/0;
       // mapper.insertSelective(loveFile);
    }

这个test3,一定会报 by zero,他会报错,我来测试下,他会不会回滚,影响他人。

如此是影响了。

因为外层事务没有声明异常,他看到一个内部报by zero 了,而自己是RuntimeException,包含在内。因此自己回滚了。

test1 是直接加入整体的。没加出数据,就说明了此问题。

我其实是像测试,test3是否回滚,这个测试案例不合适。

@RequestMapping("/test")
    @Transactional
    public void test() {
        LoveFile test1 = new LoveFile();
        test1.setFileUuid(get32Uuid());
        test1.setFileName("test1");
        loveFileService.test1(test1);
        LoveFile test3 = new LoveFile();
        test3.setFileUuid(get32Uuid());
        test3.setFileName("test3");
        loveFileService.test3(test3);
        LoveFile test2 = new LoveFile();
        test2.setFileUuid(get32Uuid());
        test2.setFileName("test2");
        loveFileService.test2(test2);
    }
   @Override
    @Transactional
    public void test1(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional
    public void test2(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
    }
    @Override
    @Transactional(rollbackFor=SQLException.class,propagation = Propagation.REQUIRES_NEW)
    public void test3(LoveFile loveFile) {
        mapper.insertSelective(loveFile);
        int  a = 1/0;
    }

我给了test3 正常的主键,让他添加成功,却在后面 by zero .因为我认为他不会回滚,这次应该是test3加进去了,test1和test2被外层事务回滚。

但是结果是:

我又做了一些测试,发现rollbackFor=SQLException.class 这样的声明一个特定的异常没有任何效果。

rollbackFor 用途不大,就是声明rollbackFor=Exception.class 时比 RuntimeException 广一些。

其他时用途不大。

如果想让特定异常不回滚,还不如用 try catch。只要异常被catch住,不被方法知道,就不会出现error ,也就不会回滚。

总结

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

相关文章

  • Java 中的抽象介绍

    Java 中的抽象介绍

    这篇文章主要介绍了Java 中的抽象,数据抽象是一种仅向用户显示基本细节的属性。不向用户显示琐碎或非必需的单元,下面文章Java抽象详细内容,需要的朋友可以参考一下
    2021-12-12
  • SpringCloud Bus消息总线的实现

    SpringCloud Bus消息总线的实现

    消息总线是一种通信工具,可以在机器之间互相传输消息、文件等,这篇文章主要介绍了SpringCloud Bus消息总线的实现,Spring cloud bus 通过轻量消息代理连接各个分布的节点,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java中的序列化和反序列化使用及解读

    Java中的序列化和反序列化使用及解读

    文章总结了Java中序列化和反序列化的概念,包括序列化的用途、实现方式和注意事项,此外,还介绍了其他几种常见的序列化框架,如JSON序列化、ProtocolBuffers和Kryo,并给出了最佳实践和使用场景建议
    2026-02-02
  • 自定义log4j2中的Appender来获取日志内容的示例代码

    自定义log4j2中的Appender来获取日志内容的示例代码

    在 Log4j2 中,Appender 是负责将日志事件输出到目标地点的组件,本文讲述的是通过 log4j 中自定义的 Appender 来获取需要打印的日志信息,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2024-02-02
  • spring-boot使用Admin监控应用的方法

    spring-boot使用Admin监控应用的方法

    本篇文章主要介绍了spring-boot使用Admin监控应用的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • mybatis框架order by作为参数传入时失效的解决

    mybatis框架order by作为参数传入时失效的解决

    这篇文章主要介绍了mybatis框架order by作为参数传入时失效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • JAVA获取当前项目和文件所在路径的实例代码

    JAVA获取当前项目和文件所在路径的实例代码

    这篇文章主要介绍了JAVA获取当前项目和文件所在路径的实例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 解决mybatisplus插入报错argument type mismatch的问题

    解决mybatisplus插入报错argument type mismatch的问题

    这篇文章主要介绍了解决mybatisplus插入报错argument type mismatch的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Java文件操作实例详解

    Java文件操作实例详解

    这篇文章主要为大家详细介绍了Java文件操作实例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • java jni调用c函数实例分享(java调用c函数)

    java jni调用c函数实例分享(java调用c函数)

    Java代码中调用C/C++代码,当然是使用JNI,JNI是Java native interface的简写,可以译作Java原生接口,下面看实例吧
    2013-12-12

最新评论