Spring事务@Transactional常见的8种失效场景(通俗易懂)

news2024/9/24 7:16:13

前言:在日常的开发过程中,多多少少会遇到Spring事务失效导致的一些事故,本篇主要通过具体的案例分析来讲解常见的8种失效的场景,让阅读者通俗易懂的明白每一种事务失效的原因,知其然并知其所以然!

目录

一、未指定回滚异常

二、异常被捕获

三、方法内部直接调用

四、异步多线程

五、使用了错误的事务传播机制

六、方法被private或者final修饰 

七、当前类没有被Spring容器托管

八、数据库不支持事务

九、总结


一、未指定回滚异常

@Transactional注解默认的回滚异常类型是运行时异常(RuntimeException),如果我们自定义了一个异常直接继承了Exception,代码如下:

public class CustomException extends Exception{}

如果@Transactional未指定异常,当程序中抛出CustomException异常则不会回滚,测试代码如下:

    @Transactional
    public void insert() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

运行结果如下:

虽然程序当中抛出了异常,但是数据库还是成功入库了,这样显然是不合理的!

所以我们需要在@Transactional指定回滚异常的类型,遇到异常就要回滚:@Transactional(rollbackFor = Exception.class)

二、异常被捕获

当抛出的异常被try-catch捕获时,事务也会失效,具体看代码:

    @Transactional(rollbackFor = Exception.class)
    public void insert(){
        try {
            Notice notice = new Notice();
            notice.setId(UUID.randomUUID().toString());
            notice.setTitle("《发布关于新版本更新的通知》");
            notice.setAuthor("管理员");
            notice.setContent("******");
            History history = new History();
            history.setId(UUID.randomUUID().toString());
            history.setContent(notice.toString());
            noticeMapper.insert(notice);
            historyMapper.insert(history);
            throw new CustomException();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

运行结果如下:

数据还是成功入库了,明显不合理!

所以我们需要主动将此异常抛出: throws CustomException

我们也可以修改catch包裹的代码,以此来达到回滚的目的。

        catch (Exception e){
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

三、方法内部直接调用

在Spring的Aop代理下,只有目标方法在外部进行调用,目标方法才会由Spring生成的代理对象来进行管理,如果是其他不包含@Transactional注解的方法中调用包含@Transactional注解的方法时候,有@Transactional注解的方法的事务会被忽略,则不会发生回滚。

    public void insert() throws CustomException {
        doSomething();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doSomething() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        noticeMapper.insert(notice);
        historyMapper.insert(history);
        throw new CustomException();
    }

运行结果如下:

数据也成功入库了,明显不合理!

只要在insert方法上面加上@Transactional注解即可。 

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {
        doSomething();
    }

四、异步多线程

这里我撰写了一个新的myService2用于保存history对象,并在myService2的方法上加上了@Async的注解,并休眠了5s。

注:主启动类需要加上@EnableAsync

    @Async
    public void save(Notice notice) throws CustomException, InterruptedException {
        System.out.println("异步任务开始...");
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
        Thread.sleep(50000);
        System.out.println("异步任务结束...");
    }

在2个插入操作都执行完毕以后,我主动抛出一个异常。 

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException, InterruptedException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");

        noticeMapper.insert(notice);

        myService2.save(notice);

        int a = 1/0;

    }

运行结果如下:

notice没有入库,history入库了

这是因为@Async注解使用的是独立线程和独立的事务,和notice的不处于同一个事务(指的是公用的同一个数据库链接)当中,所以notice回滚了,但是history入库了。

五、使用了错误的事务传播机制

先简单介绍一下Spring事务的7种传播机制

PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式继续运行
PROPAGATION_MANDATORY必须运行在已存在的事务中,否则抛出异常
PROPAGATION_REQUIRES_NEW创建一个新事务,如果已经存在一个事务,则把当前事务挂起
PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起
PROPAGATION_NEVER以非事务方式运行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等同于`PROPAGATION_REQUIRED

这边我使用的是PROPAGATION_REQUIRES_NEW的传播机制。

MyService2代码如下:

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public void save(Notice notice){
        History history = new History();
        history.setId(UUID.randomUUID().toString());
        history.setContent(notice.toString());
        historyMapper.insert(history);
    }

依旧在2个插入操作后抛出异常:

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {
        Notice notice = new Notice();
        notice.setId(UUID.randomUUID().toString());
        notice.setTitle("《发布关于新版本更新的通知》");
        notice.setAuthor("管理员");
        notice.setContent("******");

        noticeMapper.insert(notice);
        
        myService2.save(notice);

        throw new CustomException();
    }

运行结果如下: 

notice的信息入库失败,但是history成功入库了。

这是因为PROPAGATION_REQUIRES_NEW使得notice和history公用的不是同一个数据库链接,事务都是独立开来的。

六、方法被private或者final修饰 

这种情况下,事务也是会失效的。

    @Transactional(rollbackFor = Exception.class)
    private void insert() throws CustomException {

    }

    @Transactional(rollbackFor = Exception.class)
    public final void insert() throws CustomException {

    }

七、当前类没有被Spring容器托管

在当前实体类上面要打上@Service注解,否则项目启动时也会报错,不多做阐述。

//@Service
public class MyService {

    @Resource
    private HistoryMapper historyMapper;

    @Resource
    private NoticeMapper noticeMapper;

    @Transactional(rollbackFor = Exception.class)
    public void insert() throws CustomException {

    }

}

八、数据库不支持事务

比如Mysql的Myisam存储引擎是不支持事务的,只有innodb存储引擎才支持。 这个问题出现的概率极其小,了解一下。

九、总结

这就是目前日常开发当中我总结的Spring事务常见的8种失效场景,如有遗漏,欢迎评论区补充!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java——《面试题——Git篇》

全文章节 Java——《面试题——基础篇》 Java——《面试题——JVM篇》 Java——《面试题——多线程&并发篇》 Java——《面试题——Spring篇》 Java——《面试题——SpringBoot篇》 Java——《面试题——MySQL篇》​​​​​​ Java——《面试题——SpringCloud》 Java——…

初识操作系统以及Linux常用指令(上)

目录 一:操作系统简介 二:Linux简介 Linux是什么 Linux的作用 非图形化的Linux界面 Linux环境的搭建 三:Linux下的文件结构 ⭐Linux组织文件的结构为多叉树 文件路径 Linux下的隐藏文件 四:Linux基础指令 创建用户相…

实践:devops之云主机模式持续部署(ci-cd)

实践:devops之云主机模式持续部署(ci-cd) 目录 推荐文章 https://www.yuque.com/xyy-onlyone/aevhhf?# 《玩转Typora》 0、流程分析 2条Jenkins pipeline CI pipeline CD pipeline 标准规范 项目规范与总体设计 公司里面要使用流水线要做持续集成CI/CD的项目越来…

02-基础入门-数据包拓展

基础入门-数据包拓展 基础入门-数据包拓展1、http/https数据包(1)HTTP协议是什么?(2)HTTP原理(3)HTTP特点(4)URI和URL的区别(5)HTTP报文组成&…

IP路由协议(RIP、IGRP、OSPF、IS-IS、BGP)

文章目录 1、路由分类2、RIP协议1)RIP的工作原理2)RIP路由表的更新过程3)RIP路由表的更新原则4)RIP的特性5)RIP协议的版本 4、IGRP协议1)IGRP路由表的更新2)IGRP的度量标准 5、OSPF协议1&#x…

Java【动态规划】斐波那契数列模型, 图文详解 + 代码

文章目录 一、第 N 个泰波那契数1, 题目2, 思路分析2.1, 状态表示2.2, 状态转移方程2.3, 初始化2.4, 填表顺序2.5, 返回值 3, 代码 二、三步问题1, 题目2, 思路分析2.1, 状态表示2.2, 状态转移方程2.3, 初始化2.4, 填表顺序2.5, 返回值 3, 代码 三、1, 题目2, 思路分析2.1, 状态…

Redis 高可用和优化

目录 一:Redis 高可用 二: Redis 持久化 1、持久化的功能 2、Redis 提供两种方式进行持久化 3、 RDB 持久化 (1)触发条件 (1.1)手动触发 (1.2)自动触发 (1.3&am…

软件测试岗位之大厂到底有多累?

今天给大家分享两个朋友的故事,他们分别在国内两家顶尖的互联网大厂,一个在头条,一个在蚂蚁。 头条的故事 头条的主人公,在入职后的一年里,晚上十点半下班是比较早了,基本上都是十一点半左右下班&#xff…

Redmi4X刷入Ubuntu touch真正成为一台远程无需人操作的云服务器(就是配置垃圾)

前言 前几天把高一买的手机相册和文件拷贝到了电脑上,寻思这旧手机还能干嘛,搜了一下有做监控的,行车记录仪的,最后决定还是做Linux服务器香啊。用了一天时间参考网上的教程做完了,自己再做一下总结和一些弯路记录。 …

ctemplate

参考安装 原理&#xff1a; 如何使用&#xff1f; // test_ctemplate.cc #include <iostream> #include <string> #include <ctemplate/template.h>int main() {std::string in_html "./test.html";std::string value "一行白鹭上青天&q…

一、设计模式的作用和六大原则

文章目录 引言1.设计模式1.1 设计模式的目的1.2 设计模式六大原则1.2.1 单一职责原则&#xff08;类、方法和接口&#xff0c;保持职责单一性&#xff0c;如:Activity和Adapter分成两个类&#xff09;1.2.2. 开闭原则&#xff08;扩展开放&#xff0c;修改关闭&#xff1b;如&a…

python 数字进制

python 数字进制 1、数学中进制对应代码2、Python进制转换函数 1、数学中进制对应代码 十六进制&#xff1a; 0x11 八进制&#xff1a; 0o11 二进制&#xff1a; 0b11 十进制&#xff1a; 11&#xff08;啥都不加默认为十进制&#xff09; #!/usr/bin/python # -*- coding: UTF…

第133页的gtk+编程例子——编写计算器应用

第133页的gtk编程例子——编写计算器应用 以下gtk编程例子是来自书籍《实用技术&#xff1a;开发Linux应用——用GTK和GDK开发Linux图形用户界面应用》第133页的内容——编写计算器应用 例子程序是在gtk2.0编译的&#xff0c;已经修改许多地方才能在gtk3.0编译通过&#xff0c…

数学建模——插值(上)

本文是面向数学建模准备的&#xff0c;是介绍性文章&#xff0c;没有过多关于原理的说明&#xff01;&#xff01;&#xff01; 插值方法简介 插值问题 已知区间[a,b]上有系列观测值(xi,yi),i0,1,2,…,n&#xff0c;求一条曲线把这些点依次连接起来&#xff0c;称为插值&#…

在idea中高并发下的分布式锁以及解决方法

案例:1.互联网秒杀 2.抢优惠卷 3.接口幂 引入pom文件 <packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.8.RELEA…

分布式软件架构——客户端缓存

浏览器的客户端缓存 当万维网刚刚出现的时候&#xff0c;浏览器的缓存机制差不多就已经存在了。在 HTTP 协议设计之初&#xff0c;人们便确定了服务端与客户端之间“无状态”&#xff08;Stateless&#xff09;的交互原则&#xff0c;即要求客户端的每次请求是独立的&#xff…

MySQL环境搭建(Windows电脑)

MySQL环境搭建-Windows电脑篇 软件获取&#xff1a; 搜索gzh【李桥桉】&#xff0c;需要win电脑安装包&#xff0c;回复【win-MS】。 搜索gzh【李桥桉】&#xff0c;需要mac电脑安装包&#xff0c;回复【mac-MS】。 注意&#xff1a;确保电脑为64位系统&#xff08;不是的话需要…

华为手环8相册表盘使用指南

随着科技的发展&#xff0c;智能手环已经成为越来越多人的选择。华为手环8作为一款备受好评的智能手环&#xff0c;不仅具备精准的监测功能&#xff0c;还拥有丰富的表盘样式。本文将向您介绍华为手环8如何使用相册表盘&#xff0c;通过这一功能&#xff0c;您可以轻松地将您的…

Windows 打开cmd/dos窗口的12种方式(全网最全)

文章目录 1. 从开始菜单的应用列表打开2. 从搜索打开3. 从运行打开4. 从文件资源管理器打开5. 从C:\Windows\System32\cmd.exe打开6. 从桌面>快捷方式打开&#xff08;需自己创建&#xff09;7. 从任务栏>快捷方式打开&#xff08;需自己创建&#xff09;8. 从开始菜单&g…

TV快应用系列——1.ExtScreen框架快速入门

系列文章目录 TV快应用系列——1.ExtScreen框架快速入门 ExtScreen框架快速入门 系列文章目录前言一、ExtScreen简介整体结构扩展屏和应用的区别 二、安装和环境配置1.安装编辑工具2.安装Vue开发环境1.1.1安装 Node1.1.2安装配置 npm下载安装npmnpm 设置淘宝镜像安装 vue-cli&…