SpringBoot 中 @Transactional 注解的使用

news2024/12/26 23:09:49

一、基本介绍
        事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式的两种方式。本篇只说明声明式注解。

1、在 spring 项目中, @Transactional 注解默认会回滚运行时异常及其子类,其它范围之外的异常 Spring 不会帮我们去回滚数据(如果也想要回滚,在方法或者类加上@Transactional(rollbackFor = Exception.class) 即可)。异常继承体系如下图

  •  Throwable 是最顶层的父类,有 Error 和 Exception 两个子类。
    • Error:表示严重的错误(如OOM等)
    • Exception 可以分为运行时异常( RuntimeException 及其子类)和非运行时异常( Exception 的子类中,除了 RuntimeException 及其子类之外的类)。
      • 非运行时异常是检查异常( checked exceptions ),一定要 try catch,因为这类异常是可预料的,编译阶段就检查的出来。如果不抛出异常,该行代码是会报错的,项目也会启动不起来。
      • Error 和运行时异常是非检查异常( unchecked exceptions ),不需要 try catch,因为这类异常是不可预料的,编译阶段不会检查。

2、@Transactional 注解只能应用到 public 方法或者类上才有效
二、简单的使用方法
只需在方法加上 @Transactional 注解就可以了。

如下有一个保存数据的方法,加入 @transactional 注解,抛出异常之后,事务会自动回滚,数据不会插入到数据库中。

    @Override
    @Transactional
    public String save(ProductModuleConfig productModuleConfig){
        productModuleConfigDao.insert(productModuleConfig);
        if (true) {
            throw new RuntimeException("save方法运行时异常");
        }
        return "成功";
    }

我们可以从控制台日志可以看出这些信息:

该事务没有提交 commit,因为遇到 RuntimeException 异常该事务进行了回滚,数据库中也没有该条数据。

再看一个简单的使用方法:

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public String save(ProductModuleConfig productModuleConfig){
        productModuleConfigDao.insert(productModuleConfig);
        try {
            String a = null;
            boolean equals = a.equals("2");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "成功";
    }
  • save 方法无 @Transactional 注解
    • 空指针异常没有被 try catch:插入数据库操作成功
    • 空指针异常被 try catch:插入数据库操作成功
  • save 方法有 @Transactional 注解
    • 空指针异常没有被 try catch:插入数据库操作失败,回滚成功
    • 空指针异常被 try catch:插入数据库操作成功,回滚失败

三、@Transactional 注解的属性介绍
事务的传播属性(propagation 属性默认值为 Propagation.REQUIRED)

所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持以下 7 种事务传播行为:

名词解释:

  • 当天事务挂起:需要新事物时,将现有的 connection1保存起来(它还有尚未提交的事务),然后创建 connection2,connection2 提交、回滚、关闭完毕后,再把 connection1取出来,完成提交、回滚、关闭等动作,保存 connection1 的动作称之为事务挂起。

详解 Spring 的事务传播属性以及在写代码的过程中发生嵌套并发生事务失效的场景

再说这些之前,大家先要消除一个问题, Spring 的事务是怎么实现的?

Spring 本身是没有事务的,只有数据库才会有事务,而 Spring 的事务是借助 AOP,通过动态代理的方式,在我们要操作数据库的时候,实际是 Spring 通过动态代理进行功能拓展,在我们的代码操作数据库之前通过数据库客户端打开数据库事务,如果代码执行完毕没有异常信息或者是没有 Spring 要捕获的异常信息时,再通过数据库客户端提交事务,如果有异常信息或者是有 Spring 要捕获的异常信息,再通过数据库客户端程序回滚事务,从而达到控制数据库事务的目的。

四、@Transactional 注解的一些代码 demo
        比如如下代码,save 方法首先调用了 method1 方法,然后 save 方法抛出了异常,就会导致事务回滚,如下两条数据都不会插入数据库。可从控制台日志信息可以看出,没有提交(commit)事务,直接回滚掉了。

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String save(ProductModuleConfig productModuleConfig){
        method1();
        productModuleConfigDao.insert(productModuleConfig);
        if (true) {
            throw new RuntimeException("save方法运行时异常");
        }
        return "成功";
    }
 
    public void method1() {
        ProductModuleConfig productModuleConfig = new ProductModuleConfig();
        productModuleConfig.setId(UUID.randomUUID().toString());
        productModuleConfig.setName("哈哈哈哈2");
        productModuleConfigDao.insert(productModuleConfig);
    }

   

        现在有如下需求,就算 save 方法的后面抛异常了,也不能影响 method1 方法的数据插入。或许很多人的想法如下,给 method1 方法加入一个新的事务,这样 method1 就会在这个新的事务中执行,原来的事务不会影响到新的事务。比如 method1 方法上面再加入注解 @Transactional ,设置 propagation 属性为 Propagation.REQUIRES_NEW,代码如下:

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String save(ProductModuleConfig productModuleConfig){
        method1();
        productModuleConfigDao.insert(productModuleConfig);
        if (true) {
            throw new RuntimeException("save方法运行时异常");
        }
        return "成功";
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void method1() {
        ProductModuleConfig productModuleConfig = new ProductModuleConfig();
        productModuleConfig.setId(UUID.randomUUID().toString());
        productModuleConfig.setName("哈哈哈哈2");
        productModuleConfigDao.insert(productModuleConfig);
    }

         运行之后,发现数据还是没有插入数据库中。怎么回事,我们先看一下控制台日志打印信息。从日志内容可以看出,其实两个方法都是处于同一个事务中,method1 方法并没有创建一个新的事务。

        大概意思:在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。 在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截,就像上面的 save 方法直接调用了同一个类中的 method1 方法,method1 方法不会被 Spring 的事务拦截器拦截,也就是说 method1 方法上的注解是失效的,根本没起作用。

用一个示意图加深一下印象:

        看上边的示意图你一定会明白了吧,原因还是因为代理的时候,直接把有事务的方法包在了有事务的代理方法里面了,不管 method2方法是否有 @Transactional 注解,都会随着 method1() 的事务属性决定,如果 method1() 回滚,必然会导致 method2() 也会回滚。

        为了解决这个问题,我们可以新建一个类:

@Service
public class OtherServiceImpl implements OtherService {
 
    @Resource
    private ProductModuleConfigDao productModuleConfigDao;
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method() {
        ProductModuleConfig productModuleConfig = new ProductModuleConfig();
        productModuleConfig.setId(UUID.randomUUID().toString());
        productModuleConfig.setName("哈哈哈哈3");
        productModuleConfigDao.insert(productModuleConfig);
    }
 
}

         然后在 save 方法中调用 otherService.method1 方法

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String save(ProductModuleConfig productModuleConfig){
        otherService.method();
        productModuleConfigDao.insert(productModuleConfig);
        if (true) {
            throw new RuntimeException("save方法运行时异常");
        }
        return "成功";
    }

        这下,otherService.method 方法的数据插入成功,事务提交了。save 方法的数据未插入,事务回滚了。继续看一下日志内容:

        从日志可以看出,首先创建了 save 方法的事务,由于 otherService.method 方法的 @transactional 的 propagation 属性为 Propagation.REQUIRES_NEW(新建事务,如果当前存在事务,就把当前事务挂起。如果当前方法没有事务,就新建事务),所以接着暂停了 save 方法的事务,重新创建了 otherService.method 方法的事务,接着 otherService.method 方法的事务提交,method方法数据保存成功。接着 save 方法事务开始运行碰到错误将其插入的数据进行回滚,但是method方法插入的数据不会回滚。这就印证了只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。

总结:

  • 在同一个类中事务嵌套的话,最终的结果应该是取决于最外层的方法事务的传播特性。
  • 如果是不同的类的事务嵌套的话,外层的方法按照外层的事务传播属性执行,内层的传播属性按照内层的传播属性的特点去运行。

五、@Transactional 注解失效场景
1、@Transactional 注解应用在非 public 修饰的方法上,导致注解失效
        protected、private 修饰的方法上使用 @Transactional 注解,事务是无效

2、propagation 设置错误,导致注解失败
        propagation 属性设置为 PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、 PROPAGATION_NEVER 这三种类别时,@Transactional 注解就不会产生效果。

3、rollbackFor 设置错误,@Transactional 注解失败
        Spring 默认回滚事务分别为抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)和 Error 两种情况,其他异常不会回滚,希望抛出其他异常 Spring 亦能回滚事务,需要指定 rollbackFor 属性

4、方法之间的互相调用导致 @Transactional 失效(在同一个 Service 中)

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String save(ProductModuleConfig productModuleConfig){
        method1();
        productModuleConfigDao.insert(productModuleConfig);
        if (true) {
            throw new RuntimeException("save方法运行时异常");
        }
        return "成功";
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void method1() {
        ProductModuleConfig productModuleConfig = new ProductModuleConfig();
        productModuleConfig.setId(UUID.randomUUID().toString());
        productModuleConfig.setName("哈哈哈哈2");
        productModuleConfigDao.insert(productModuleConfig);
    }

5、异常被 catch 捕获导致 @Transactional 注解失效

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public String save(ProductModuleConfig productModuleConfig){
        try {
            productModuleConfigDao.insert(productModuleConfig);
            method2();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "成功";
    }
 
    public void method2(){
        String a = null;
        boolean equals = a.equals("2");
    }

        method2 方法是会报空指针异常,而 save 方法对其进行了 try catch 了method2 方法的异常,那 save 方法的事务就不能正常回滚,数据还是会插入到数据库中的,最终会报 method2 方法的空指针异常。

6、数据库引擎不支持事务
        这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

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

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

相关文章

【HarmonyOS】深入了解 ArkUI 的动画交互以提高用户体验

从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是…

echarts柱状图加单位,底部文本溢出展示

刚开始设置了半天都不展示单位,后来发现是被挡住了,需要调高top值 // 基于准备好的dom,初始化echarts实例var myChart echarts.init(document.getElementById("echartD"));rankOption {// backgroundColor: #00265f,tooltip: {…

让企业的招投标文件、生产工艺、流程配方、研发成果、公司计划、员工信息、客户信息等核心数据更安全。

PC端访问地址1:www.drhchina.com PC端访问地址2: https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 全方位立体式防护  让数据泄密无处遁形 信息防泄漏是一项系统的整体部署工程,加密监控已成为多数企事业单…

个人优势能力测评 (多元智能版)

个人优势能力测评也叫多元智能测评,有美国心理学家加德纳Gardner博士在1983年提出多元智能理论,多元智能是相对于智商来说的,多元智能理论认为人类的智商不是单一的,而是多种类型的组合,每个人都是组合了不同的优势&am…

如何在CentOS安装SQL Server数据库并通过内网穿透工具实现公网访问

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库,并结合cpolar内网穿透工具&#xff0…

第四站:指针的进阶-(二级指针,函数指针)

目录 二级指针 二级指针的用途 多级指针的定义和使用 指针和数组之间的关系 存储指针的数组(指针数组:保存地址值) 指向数组的指针(数组指针) 传参的形式(指针) 数组传参时会退化为指针 void类型的指针 函数指针 定义: 调用:两种方式:(*指针名)(参数地址) 或者 指针…

JavaScript的闭包、执行上下文、到底是怎么回事?还有必要学吗?

在上一课,我们了解了 JavaScript 执行中最粗粒度的任务:传给引擎执行的代码段。并且,我们还根据“由 JavaScript 引擎发起”还是“由宿主发起”,分成了宏观任务和微观任务,接下来我们继续去看一看更细的执行粒度。 一…

数据结构与算法-栈-移掉K位数字

移掉K位数字 给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k **位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。 示例 1 : 输入:num "1432219", k 3 输出:&quo…

葡萄酒的独特国度智利

葡萄酒的独特国度智利 一、独特的智利葡萄酒 智利葡萄酒是在20世纪90年代以后才逐渐走向了世界,由于低税、口味独特等优点,深受大众喜爱。因为智利独待的气候其生产的葡萄别有风味,为其产出优质葡萄酒奠定了基础,再加上欧洲古老的…

电口模块SFP-GE-T:实现光口与电口之间的转换

电口模块是一种用于实现光口转电口功能的设备,在网络通信中起到重要作用。电口模块没有光电转换的过程,只是传输电信号。本文介绍电口模块的作用、分类、以及使用方法。 一、什么是电口模块 电口模块又被称为光转电模块,它是一种支持热插拔…

vue3+echarts应用——深度遍历html的dom结构并用树图进行可视化

文章目录 ⭐前言💖vue3系列文章 ⭐html数据解析💖 html字符串转为html对象💖 深度遍历html对象内容 ⭐echarts 树图的渲染💖 处理html内容为树状结构💖 渲染树状图💖 inscode代码块 ⭐总结⭐结束 ⭐前言 大…

程序员必知!备忘录模式的实战应用与案例分析

备忘录模式允许在不破坏封装性下捕获并在外部保存对象状态,支持状态恢复,常用于撤销、历史记录等功能。例如在线文档编辑器的撤销操作,编辑器作为原发起人记录状态并提供保存与恢复方法,历史记录或撤销为管理者,保存备…

Vue3打印插件Print.js的使用

文章目录 一、Print.js二、安装2.1、 js文件2.2、npm2.3、CDN 三、使用3.1、网页(HTML)打印3.2、PDF 打印3.3、图像打印3.4、JSON 打印 四、最后 一、Print.js 在使用 Print.js 插件之前,我们可以通过下面的链接先了解和认识一下这个 JavaScr…

Linux下Redis6下载、安装和配置教程-2024年1月5日

Linux下Redis6下载、安装和配置教程-2024年1月5日 一、下载二、安装三、启动四、设置开机自启五、Redis的客户端1.Redis命令行客户端2.windows上的图形化桌面客户端 一、下载 1.Redis的官方下载:https://redis.io/download/ 2.网盘下载: 链接&#xff…

基于FPGA的万兆以太网学习(1)

万兆(10G) 以太网测速视频:FPGA 实现UDP万兆以太网的速度测试 1 代码结构 2 硬件需求 SFP+屏蔽笼可以插入千兆或万兆光模块。SFP+信号定义与 SFP 一致。 3 Xilinx IP 10 Gigabit Ethernet Subsystem IP说明 文章链接: Xilinx IP 10 Gigabit Ethernet Subsystem IP 4 E…

【ZooKeeper高手实战】ZooKeeper常用命令及客户端工具Curator核心功能

🌈🌈🌈🌈🌈🌈🌈🌈 欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理…

答疑解惑:核技术利用辐射安全与防护考核

前言 最近通过了《核技术利用辐射安全与防护考核》,顺利拿到了合格证。这是从事与辐射相关行业所需要的一个基本证书,考试并不难,在此写篇博客记录一下主要的知识点。 需要这个证书的行业常见的有医疗方面的,如放疗,…

寒假护眼台灯哪款更好?五款高品质护眼台灯推荐

近年来学生近视的现象越来越严重了,而且近视的年龄也越来越小了,不少还没开始上小学的孩子,就已经戴上了厚厚的近视眼镜。而那些高年级的学生更是近视的重灾区,不仅每天需要高强度的学习和长时间用眼,甚至晚上都还需要…

Java内存模型(JMM)是基于多线程的吗

Java内存模型(JMM)是基于多线程的吗 这个问题按我的思路转换了下,其实就是在问:为什么需要Java内存模型 总结起来可以由几个角度来看待「可见性」、「有序性」和「原子性」 面试官:今天想跟你聊聊Java内存模型&#…

【pdf密码】pdf打印密码强制解除

正常的PDF文件是可以打印的,如果PDF文件打开之后发现文件不能打印,我们需要先查看一下自己的打印机是否能够正常运行,如果打印机是正常的,我们再查看一下,文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…