# Spring 事务失效场景

news2024/12/23 3:27:10

Spring 事务失效场景

文章目录

  • Spring 事务失效场景
    • 前言
    • 事务不生效
      • 未开启事务
      • 事务方法未被Spring管理
      • 访问权限问题
        • 基于接口的代理
          • 源码解读
        • CGLIB代理
      • 方法用final修饰
      • 同一类中的方法调用
      • 多线程调用
      • 不支持事务
    • 事务不回滚
      • 设置错误的事务传播机制
      • 捕获了异常
      • 手动抛了别的异常
      • 自定义了回滚异常
      • 事务被手动提交
    • 其它
      • 大事务问题
        • 缩小事务范围
        • 手动提交事务
        • 异步处理
      • 事务的性能和并发性

前言

# Spring事务详解

  1. Spring事务是用于解决数据库操作中的一致性和隔离性问题的机制。数据库事务是一组操作,要么全部成功执行,要么全部回滚,以确保数据的完整性和一致性。
  2. Spring事务管理的主要目的是确保在多个数据库操作中,要么所有操作都成功提交,要么所有操作都回滚,从而保持数据的一致性。它提供了以下几个方面的解决方案:
  • 原子性(Atomicity):事务要么全部成功执行,要么全部回滚,确保数据库操作的原子性。

  • 一致性(Consistency):事务在执行前后,数据库的状态应保持一致。如果事务执行失败,数据库应该回滚到事务开始之前的状态。

  • 隔离性(Isolation):事务应该在相互之间隔离,以避免并发操作引起的问题。它确保了在并发环境下,每个事务都能够独立地执行,并且不会相互干扰。

  • 持久性(Durability):一旦事务提交,其结果应该持久保存在数据库中,即使发生系统故障或重启。

  1. Spring事务管理通过使用注解或编程方式来定义事务边界,它可以应用于各种数据访问技术(如JDBCHibernateJPA等)。它还提供了不同的传播行为和隔离级别,以满足不同的业务需求。
  2. 通过使用Spring事务,开发人员可以简化数据库操作的管理,并确保数据的一致性和可靠性,从而提高应用程序的可靠性和性能。

事务不生效

未开启事务

  • 如果使用的是springboot项目,springboot通过DataSourceTransactionManagerAutoConfiguration类,默认开启了事务。只需要配置spring.datasource相关参数即可。
    在这里插入图片描述
  • 如果使用的是传统的spring项目,则需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

事务方法未被Spring管理

  • 未将类标记为 Spring 管理的组件:确保类被标记为 @Service@Component 或其他适当的注解,以便 Spring 能够扫描并管理该类。
  • 未使用 @Transactional 注解标记事务方法:确保需要进行事务管理的方法被标记为 @Transactional 注解,以便 Spring 能够识别并应用事务管理。

访问权限问题

基于接口的代理
  • 当使用基于代理的事务管理时,Spring会在运行时生成一个代理对象来管理事务。这个代理对象会拦截被注解的方法,并在方法执行前后进行事务的开启、提交或回滚等操作。
  • 默认情况下,Spring的事务代理是基于接口实现的,因此只有public方法才能被代理。如果事务方法是privateprotected或默认访问级别的,Spring无法生成代理对象,从而导致事务不生效。
/**
 * 不生效
 */
@Transactional
private void ransactionOne() {
    User user = new User();
    user.setUsername("张三");
    userMapper.insertUser(user);
    methodOne();
    int a = 3 / 0;
    logger.info(String.valueOf(a));
}
源码解读
  • 判断是否是public
    在这里插入图片描述

  • 有事务的类
    在这里插入图片描述

CGLIB代理
  • 如果使用基于类的代理,即使用CGLIB代理,Spring可以代理非public方法。可以通过配置 proxy-target-class 属性为true来启用基于类的代理。
  • 如果使用的是基于类的代理,事务方法可以是非public的,但需要启用基于类的代理。
  • SpringBoot 事务示例:通过在 @EnableTransactionManagement 注解上设置 proxyTargetClass 属性为true来启用基于类的代理。
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {
    // 配置其他的Bean和组件
}
  • 使用基于类的代理时,非public方法也可以被代理。但需要注意,启用基于类的代理可能会带来一些性能开销,因此只有在确实需要代理非public方法时才应使用。
  • 确保在配置类上添加了 @EnableTransactionManagement 注解,并根据需要设置 proxyTargetClass 属性,就可以在Spring Boot中使用基于类的代理来进行事务管理了。
  • Spring使用CGLIB库的Enhancer类来生成代理对象,生成的代理对象中包含EnhancerBySpringCGLIB作为标识符
    在这里插入图片描述

方法用final修饰

  • Spring 中,事务是通过动态代理来实现的。当一个类被代理时,Spring 会创建一个代理对象来包装原始对象,从而在方法调用前后添加事务处理逻辑。然而,对于 final 方法,由于无法重写,因此无法创建代理对象,事务管理器也就无法对其进行事务处理。
@Service
public class UserService {

    @Transactional
    public final void add(UserModel userModel){
        saveData(userModel);
        updateData(userModel);
    }
}

同一类中的方法调用

  • this是被真实对象,所以会直接走methodTwo的业务逻辑,而不会走切面逻辑,所以事务失败。
/**
 * 同一个类中的方法调用
 */
@Override
public void transactionSix() {
    User user = new User();
    user.setUsername("张三");
    userMapper.insertUser(user);
    // 没有事务的方法调用有事务的方式,相当于使用 this 调用不会走 AOP 的逻辑
    methodTwo();
}

@Transactional(rollbackFor = Throwable.class)
public void methodTwo() {
    User user = new User();
    user.setUsername("李四");
    userMapper.insertUser(user);
    int a = 5 / 0;
    logger.info(String.valueOf(a));
}
  • 解决方法可以是在方法上添加@Transactional注解
  • @EnableAspectJAutoProxy(exposeProxy = true)在启动类中添加,会由Cglib代理实现。
  • 如果只想让methodTwo的事务生效,可以把methodTwo写到一个新的service,用service调用
/**
 * 同一个类中的方法调用,调用 service 的方法,insetUser 方法事务可以生效
 */
@Override
public void transactionSeven(){
    User user = new User();
    user.setUsername("张三");
    userMapper.insertUser(user);
    transactionHelpService.insetUser();
}

@Transactional(rollbackFor = Throwable.class)
@Override
public void insetUser() {
    User user = new User();
    user.setUsername("李四");
    userMapper.insertUser(user);
    int a = 5 / 0;
    logger.info(String.valueOf(a));
}

多线程调用

  • 同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

不支持事务

  • 数据库本身无法支持事务的场景下,例如使用MysqlMyISAM引擎

  • MySQL5.5往后,默认采用InnoDB存储引擎,在这之前采用MyISAM存储引擎

  • InnoDBMySQL默认事务型引擎 ,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)

  • MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM 不支持事务、行级锁、外键 ,有一个毫无疑问的缺陷就是崩溃后无法安全恢复

事务不回滚

设置错误的事务传播机制

  • Propagation.NEVER:这种类型的传播特性不支持事务,如果有事务则会抛异常。
  • 目前只有这三种传播特性才会创建新事务:REQUIREDREQUIRES_NEWNESTED

捕获了异常

  • 在代码中手动try...catch了异常
@Transactional
@Override
public void testOne() {
    try {
        User user = new User();
        user.setUsername("张三");
        userMapper.insertUser(user);
        int a = 3 / 0;
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
}
  • Spring Boot中,事务是通过AOP(面向切面编程)机制实现的。当使用 @Transactional 注解标记一个方法时,Spring会在方法开始前创建一个事务,并在方法执行结束后根据方法的执行结果来决定是否提交或回滚事务。
  • 如果在方法执行期间发生异常,Spring会捕获该异常并将事务标记为“回滚”。这意味着在方法执行结束后,Spring会自动回滚该事务并撤销对数据库的任何更改。
  • 如果在方法中捕获了异常并处理了它,那么Spring就无法感知到该异常,并且不会将事务标记为“回滚”。这意味着在方法执行结束后,Spring会将事务提交,而不是回滚,这可能会导致不一致的数据状态。

手动抛了别的异常

  • spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。
  • 如果在执行方法时抛出了 RuntimeException 类型的异常,Spring 会认为这个异常是不可恢复的,也就是说无法通过异常处理来修复。在这种情况下,Spring 会回滚事务,撤销之前的所有数据库操作,以保证数据的一致性。
  • 如果抛出的是非 RuntimeException 类型的异常,Spring 会认为这个异常是可以恢复的,意味着可以通过异常处理来修复。在这种情况下,Spring 不会回滚事务,而是允许应用程序继续执行。
  • 这个机制的主要目的是保护数据的一致性。在大多数情况下,如果发生了非 RuntimeException 类型的异常,应用程序可能会尝试通过其他方式来处理异常并继续执行。而如果发生了 RuntimeException 类型的异常,应用程序可能无法继续执行,因此需要回滚事务以撤销之前的数据库操作。
/**
 * 在代码中手动抛出别的异常 事务不回滚
 */
@Override
public void testTwo() throws Exception {
    try {
        User user = new User();
        user.setUsername("张三");
        userMapper.insertUser(user);
        int a = 3 / 0;
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
        throw new Exception("test");
    }
}

自定义了回滚异常

  • 在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数

  • rollbackFor 参数指定了回滚事务异常的类型,需要检查抛出的异常是否是指定的异常,如果不一致则回滚不了
    在这里插入图片描述

  • 开发规范中会提示需要指定rollbackFor参数,事务默认只会在捕获到未被处理的 RuntimeException 或 Error 时才会回滚。如果你的代码中捕获了异常并进行了处理,但没有再次抛出 RuntimeException 或 Error,事务将不会回滚。确保在捕获异常时,将异常重新抛出或手动触发回滚。

事务被手动提交

  • 如果在代码中手动调用了 commit() 方法来提交事务,而没有调用 rollback() 方法来回滚事务,事务将不会回滚。确保在需要回滚的情况下,正确地调用了回滚方法。

其它

大事务问题

缩小事务范围
  • 将大事务拆分为多个小事务,并使用嵌套事务来管理它们
手动提交事务
  • 如果需要更细粒度的控制,可以在代码中手动管理事务。使用 TransactionTemplate 类可以手动启动、提交或回滚事务
异步处理
  • 对于非关键的操作,你可以考虑使用异步处理来减少事务的时间。
  • 将一些长时间运行的操作放在异步任务中,这样可以减少事务的持有时间,从而减少事务的大小和影响范围。

事务的性能和并发性

  • 大事务可能会导致性能问题和并发性竞争。在设计事务时,要考虑事务的持有时间和锁竞争情况。尽量将事务范围缩小到最小,避免长时间持有事务锁,以提高性能和并发性。

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

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

相关文章

c++ pcl点云变换骨架枝干添加树叶源码实例

程序示例精选 c pcl点云变换骨架枝干添加树叶源码实例 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《c pcl点云变换骨架枝干添加树叶源码实例》编写代码,代码整洁,…

搞个微信小程序002:个人信息

新建一个用于&#xff0c;和001中一样&#xff0c;然后&#xff0c;就改掉两个文件&#xff1a; index.wxml: <view><!-- 头像区域 --><view class"top"><view class"user-img"><image src"/images/tx.png"><…

JIT耗时优化

优质博文&#xff1a;IT-BLOG-CN 一、背景 业务流量突增&#xff0c;机器直接接入大量流量QPS2000&#xff0c;JIT和GC会消耗太多CPU资源&#xff0c;导致1-2分钟时间内的请求超时导致异常&#xff0c;因此采用流量预热的方式&#xff0c;让机器逐步接入流量&#xff0c;需要预…

学习MAVEN

MAVEN的详细介绍和作用、意义 好的&#xff0c;小朋友们&#xff0c;我们今天来聊聊一个非常神奇的工具箱&#xff0c;它的名字叫做Maven! &#x1f31f; 1. **神奇的工具箱Maven**: Maven就像是一个神奇的工具箱&#x1f9f0;&#xff0c;它可以帮助大人们把他们的电脑工…

手撕 视觉slam14讲 ch13 代码(7)后端优化 Backend::Optimize()

在上一篇 手撕&#xff08;6&#xff09;中的InsertKeyframe()插入关键帧的函数里&#xff0c;有一个 Backend::UpdateMap() 函数 &#xff0c;从这里通过条件变量 map_update_ 来激活后端优化。 backend.h&#xff1a; // * 有单独优化线程&#xff0c;在Map更新时启动优化…

【JavaScript】深入浅出理解事件循环

1. 浏览器的进程模型 1.1 进程 程序运行需要有它自己专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程。 每个应用至少有一个进程&#xff0c;进程之间相互独立&#xff0c;即使要通信&#xff0c;也需要双方同意。 1.2 线程 有了进程后&#xff0c;就可以运…

《王道计算机考研——操作系统》学习笔记总目录+思维导图

本篇文章是对《王道计算机考研——操作系统》所有知识点的笔记总结归档和计算机网络的思维导图 学习视频&#xff1a;王道计算机考研 操作系统 408四件套【计网、计组、操作系统、数据结构】完整课堂PPT 思维导图 &#xff08;求Star~&#xff09;&#xff1a;【王道考研】计…

NodeJS VM沙箱逃逸

文章目录 基本概念Node将字符串执行为代码方法一 eval方法二&#xff1a;new Function Nodejs作用域vm沙箱逃逸vm沙箱逃逸的一些其他情况实例 基本概念 什么是沙箱&#xff08;sandbox&#xff09;当我们运行一些可能会产生危害的程序&#xff0c;我们不能直接在主机的真实环境…

Promise详解:手写Promise底层-实现Promise所有的功能和方法

前言 目标&#xff1a;封装一个promise&#xff0c;更好的理解promise底层逻辑需求&#xff1a;实现以下promise所有的功能和方法 如下图所示一、构造函数编写 步骤 1、定义一个TestPromise类&#xff0c; 2、添加构造函数&#xff0c; 3、定义resolve/reject&#xff0c; 4、…

FL Studio21中文版本好用吗?值不值得下载

今天&#xff0c;我从一个FL Studio忠实且还算资深的用户角度&#xff0c;来为大家深度介绍并评测一下FL Studio的性能以及我四年的使用感受。 FL Studio是一款集剪辑、编曲、录音、混音一体的全能DAW&#xff08;数字音频工作站&#xff09;。其所有界面都是支持100%矢量化的…

Pycharm设置快捷键

本文主要讲一下Pycharm如何设置字体的缩小和放大的快捷键。 参见&#xff1a; 编程的快乐&#xff0c;你想象到了吗&#xff1f;PyCharm插件大全&#xff08;适合所有JetBrains家族产品&#xff09;_哔哩哔哩_bilibili

虚函数实例

1.声明&#xff1a;virtual 同名成员名 可实现父类访问子类中与其同名的成员 #include<iostream> using namespace std; class A{protected:int x;public:A(int x10):x(x1){}virtual void print(){//在类A中定义print为虚函数cout<<"A类中的x"<<x…

vue项目编译、打包、部署服务器运行

在vue项目执行npm run build,生成dis目录 打包dis上传 安装npm install -g http-server或者apt install node-http-server 运行http-server

微信小程序scroll-view设置display:flex后子view宽度设置无效解决

如果scroll-view设置了display:flex&#xff0c;子view设置宽度值无效&#xff0c;宽度值都是随着内容多少而改变&#xff1a; 效果和wxml&#xff1a; css: 原因&#xff1a;flex布局元素的子元素&#xff0c;自动获得了flex-shrink的属性 解决办法&#xff1a; 给子view增加…

国内智能客服机器人都有哪些?

随着人工智能技术的不断发展&#xff0c;智能客服机器人已经成为了企业客户服务的重要工具。国内的智能客服机器人市场也迎来了飞速发展&#xff0c;越来越多的企业开始采用智能客服机器人来提升客户服务效率和质量。 在这篇文章中&#xff0c;我将详细介绍国内知名的智能客服机…

大模型之Chat Markup Language

背景 在笔者应用大模型的场景中&#xff0c;对话模型(即大模型-chat系列)通常具有比较重要的地位&#xff0c;我们通常基于与大模型进行对话来获取我们希望理解的知识。然而大模型对话是依据何种数据格式来进行训练的&#xff0c;他们的数据为什么这么来进行组织&#xff0c;本…

7种典型的钢结构BIM应用

钢铁的工作流程往往会造成项目各个阶段信息缺乏、成本高、效率低等问题。 BIM技术通过数字化真实信息模拟建筑&#xff0c;通过中央文档共享信息&#xff0c;将流程的各个阶段紧密联系起来&#xff0c;交换信息&#xff0c;提高效率&#xff0c;降低成本。 制造专用软件不断发展…

pytorch C++ 移植

文章目录 前言安装 libtorch安装 opencv&#xff08;C&#xff09;模型转换通过跟踪转换为 Torch Script通过注解转换为 Torch Script 编写 C 代码编译环境搭建C 库管理方法一&#xff1a;手动配置 visual studio 环境方法二&#xff1a;cmake 配置环境 python 调用 C 程序 前言…

go语言Array 与 Slice

有的语言会把数组用作常用的基本的数据结构&#xff0c;比如 JavaScript&#xff0c;而 Golang 中的数组(Array)&#xff0c;更倾向定位于一种底层的数据结构&#xff0c;记录的是一段连续的内存空间数据。但是在 Go 语言中平时直接用数组的时候不多&#xff0c;大多数场景下我…

MySQL中查询重复字段的方法和步骤是怎样

示例 accountinfo 表数据如下&#xff1a; 场景一 单个字段重复数据查找 & 去重 我们要把上面这个表中 单个字段 account字段相同的数据找出来。 思路 分三步 简述&#xff1a; 第一步 要找出重复数据&#xff0c;我们首先想到的就是&#xff0c;既然是重复&#xff0c…