SpringBoot 中 @Transactional 注解的使用

一、基本介绍
        事务管理是应用系统开发中必不可少的一部分。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@Transactionalpublic String save(ProductModuleConfig productModuleConfig){productModuleConfigDao.insert(productModuleConfig);if (true) {throw new RuntimeException("save方法运行时异常");}return "成功";}

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

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

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

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)@Overridepublic 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 {@Resourceprivate ProductModuleConfigDao productModuleConfigDao;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic 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)@Overridepublic 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.rhkb.cn/news/233275.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Leetcode】移除后集合的最多元素数

目录 💡题目描述 💡思路 💡总结 100150. 移除后集合的最多元素数 💡题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素,同时从 …

SCADE—产品级安全关键系统的MBD开发套件

产品概述 随着新能源三电、智能驾驶等新技术的应用,汽车中衍生出很多安全关键零部件,如BMS、VCU、MCU、ADAS等,相应的软件在汽车中的比重越来越大,并且安全性、可靠性要求也越来越高。ANSYS主要针对安全关键零部件的嵌入式产品级软…

04-微服务-Nacos

Nacos注册中心 国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。 1.1.认识和安装Nacos Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在…

[Kubernetes]4. 借助腾讯云TKE快速创建Pod、Deployment、Service部署k8s项目

前面讲解了通过命令行方式来部署k8s项目,下面来讲讲通过腾讯云TKE来快速创建Pod、Deployment、Service部署k8s项目,云平台搭建Kubernetes可参考[Kubernetes]1.Kubernetes(K8S)介绍,基于腾讯云的K8S环境搭建集群以及裸机搭建K8S集群 一.通过腾讯云TKE创建集群 1.创建集群 参考上…

误删除的备忘录恢复方法是什么?备忘录不小心删除了怎么找回?

有不少小伙伴在使用手机的过程中,想要随手记录一些琐事或容易忘记的事情,使用手机系统备忘录或便签等记事工具是非常便捷的。不过在日积月累的使用过程中,备忘录中记录的内容就会越来越多,为了高效使用它,就需要定期删…

python 各级目录文件读取

目录结构 import pytestdef test_01():# 同级文件with open(1.txt, r, encodingutf-8) as file:content file.read()print(content)def test_02():# 同级目录的下的文件with open(rupfile/2.txt, r, encodingutf-8) as file:content file.read()print(content)def test_03():…

Android kotlin build.gradle.kts配置

1. 添加 maven 仓库 1. 1. settings配置 1. 1.1. settings.gradle repositories {maven {url https://maven.aliyun.com/repository/public/}mavenCentral() }1. 1.2. settings.gradle.kts repositories {maven {setUrl("https://maven.aliyun.com/repository/public/…

56K star!一键拥有跨平台 ChatGPT 应用:ChatGPT-Next-Web

前言 现在围绕 openai 的客户端层出不穷,各路开发大神可以说是各出绝招,我也试用过几个国内外的不同客户端。 今天我们推荐的开源项目是目前我用过最好的ChatGPT应用,在GitHub超过56K Star的开源项目:ChatGPT-Next-Web。 ChatGP…

MySQL取出N列里最大or最小的一个数据

如题,现在有3列,都是数字类型,要取出这3列里最大或最小的的一个数字 -- N列取最小 SELECT LEAST(temperature_a,temperature_b,temperature_c) min FROM infrared_heat-- N列取最大 SELECT GREATEST(temperature_a,temperature_b,temperat…

李宏毅机器学习第二十三周周报 Flow-based model

文章目录 week 23 Flow-based model摘要Abstract一、李宏毅机器学习1.引言2.数学背景2.1Jacobian2.2Determinant2.3Change of Variable Theorem 3.Flow-based Model4.GLOW 二、文献阅读1. 题目2. abstract3. 网络架构3.1 change of variable formula3.2 Coupling layers3.3Prop…

three.js实现模型扫光效果

three.js实现模型扫光效果 预览 关键点 在材质的onBeforeCompile回调函数中修改模型颜色在render函数中修改y&#xff08;高度&#xff09; 代码 <template><div class"app"><div ref"canvesRef" class"canvas-wrap"><…

Nginx 搭建可道云网盘

目录 1.安装php-fpm 2. 建站点根目录与配置 2.1 建站点根目录 2.2 配置 3. 搭建成功 1.安装php-fpm nginx 需要使用php 需要安装php-fpm yum install php-fpm php-mbstring php-mysqlnd php-gd -y 修改 www.conf 文件的配置29行和41行&#xff0c;将用户会让用户组改成n…

vmlinux, vmlinux.bin, bzImage; cmake的find_package(Clang)新增了哪些变量( 比较两次记录的所有变量差异)

vmlinux, vmlinux.bin, bzImage cd /bal/linux-stable/ file vmlinux #vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=b99bbd9dda1ec2751da246d4a7ae4e6fcf7d789b, not stripped #文件大小 20MB, 19940148Bfile ar…

vue-springboot基于java的社区志愿者活动信息管理系统 e2y4d

社区志愿者信息管理系统的主要开发目标如下&#xff1a; &#xff08;1&#xff09;对零碎化、分布散的数据信息进行收纳、整理&#xff0c;通过网络服务平台使这些信息内容更加调理&#xff0c;更加方便化和清晰化&#xff0c;让访问该系统的每个用户享受浏览的过程。 &#x…

Kafka(五)生产者

目录 Kafka生产者1 配置生产者bootstrap.serverskey.serializervalue.serializerclient.id""acksallbuffer.memory33554432(32MB)compression.typenonebatch.size16384(16KB)max.in.flight.requests.per.connection5max.request.size1048576(1MB)receive.buffer.byte…

如何使用css隐藏掉滚动条

1.解决方案 在滚动元素上再包裹一个父元素&#xff0c;然后&#xff0c;该元素添加如下代码&#xff1a; &#xff08;注&#xff1a;PC端浏览器滚动条为8px&#xff09;使元素偏移原来位置8px&#xff0c;目的就是将滚动条区域移动到父元素边框外面&#xff0c;然后&#xff…

CentOS 7.6的HTTP隧道代理如何支持移动设备和远程用户

在CentOS 7.6上配置HTTP隧道代理以支持移动设备和远程用户&#xff0c;需要考虑到移动网络的特点以及远程用户的需求。以下是一些关键步骤和策略&#xff0c;可以帮助你实现这一目标。 1. 优化移动设备体验 压缩数据&#xff1a;HTTP隧道代理可以用于压缩进出移动网络的数据&…

【51单片机】点亮第一个LED灯(含创建文件等基础操作)

51单片机现在不仅是电子信息专业学生的必修课&#xff0c;也是进入嵌入式领域的踏脚石。 本系列将会按照江科大的视频进行&#xff0c;也算是相当于一个笔记&#xff0c;进行巩固 实现第一个LED灯的点亮其实并不复杂&#xff0c;重要的是有一些准备工作比较繁琐&#xff0c;就…

构建网络信息安全的中国方案 - 国密SSL协议介绍以及国密Nginx服务器部署

国密SSL协议 国密SSL协议指的是采用国密算法&#xff0c;符合国密标准的安全传输协议。简而言之&#xff0c;国密SSL就是SSL/TLS协议的国密版本。TLS协议定义有三个版本号&#xff0c;为0x0301、0x0302、0x0303&#xff0c;分别对应TLS 1.0、1.1、1.2。国密SSL为了避免冲突&am…

如何利用Oracle官方网站不登录账号下载和安装非最新版本的JDK(版本自由选择)

一、JDK概述 JDK&#xff08;Java Development Kit&#xff09;是Java开发工具集&#xff0c;是针对Java编程语言的软件开发环境。它包含了Java编译器、JRE&#xff08;Java运行时环境&#xff09;以及其他一些用于开发、调试和测试Java应用程序的工具&#xff0c;是Java开发人…