当前位置: 首页 > news >正文

spring事务管理之@Transactional

spring事务管理之@Transactional

  • spring事务管理之@Transactional
    • 1、概述
    • 2、适用范围
    • 3、核心属性
      • propagation (传播行为) 深度解析:
      • rollbackFor (回滚规则) 的关键点:
    • 4、失效场景
      • (1)作用的方法不是 public
      • (2)同一个类中方法互相调用
      • (3)异常被try-catch捕获
      • (4)rollbackFor属性设置错误
      • (5)数据库引擎不支持事务
    • 5、补充场景
    • 6、最佳实践

spring事务管理之@Transactional

1、概述

在Spring Boot项目开发过程中,我们往往会遇到这样的场景:一个方法里执行了多步数据库操作,但中间某一步出错,导致数据出现“半成功半失败”的情况。这样的数据不一致问题,可能会带来严重的业务风险。而 @Transactional 就是为了解决这个问题而生,它的核心作用就是 要么所有操作都成功,要么全部作废,保证数据库的完整性。


2、适用范围

  • 类 (Class):当注解在类上时,表示该类所有 public 方法都应用相同的事务配置。

  • 方法 (Method):当方法和类上都有注解时,方法级别的注解会覆盖类级别的配置。 这是更精细的控制方式。

  • 接口 (Interface):不推荐使用。虽然技术上可行(如果使用 JDK 动态代理),但这是一种糟糕的实践。因为它会将事务这种实现细节耦合到接口定义中。如果切换到 CGLIB 代理(基于类的代理),接口上的注解会直接失效。

示例

    @Transactionalpublic void createTask(TaskDetails taskDetails) {taskRepo.save(taskDetails);}

最佳实践:始终将 @Transactional 注解在具体的实现类或其方法上。


3、核心属性

属性说明常用值/重要说明
propagation事务传播行为 (最重要)。定义当一个事务方法调用另一个事务方法时,事务如何交互。REQUIRED (默认), REQUIRES_NEW, NESTED
isolation事务隔离级别。定义事务并发问题的处理能力(脏读、不可重复读、幻读)。READ_COMMITTED (常用), REPEATABLE_READ (MySQL默认)
readOnly只读事务。设置为 true 可进行性能优化,数据库会跳过一些锁定和日志记录。true, false (默认)
timeout事务超时 (秒)。超过指定时间未完成,事务将自动回滚。整数值,如 30
rollbackFor指定回滚的异常。定义哪些异常类型会触发回滚。Exception.class (强烈推荐)
noRollbackFor指定不回滚的异常。定义哪些异常类型不会触发回滚。业务自定义的非关键异常

propagation (传播行为) 深度解析:

  • REQUIRED (默认):如果当前有事务,就加入;如果没有,就新建一个。这是最常见的场景。

  • REQUIRES_NEW:总是创建一个全新的、独立的事务。如果外部已有事务,会将其挂起。常用于需要独立提交的日志记录等场景。即使内部事务回滚,也不会影响外部事务。

  • NESTED:(注意:与REQUIRED不同!) 如果当前有事务,则创建一个嵌套事务(保存点 Savepoint)。嵌套事务是外部事务的一部分,它回滚不会影响外部事务。但如果外部事务回滚,嵌套事务也必须回滚。这需要数据库驱动支持保存点特性。


rollbackFor (回滚规则) 的关键点:

Spring 默认只在捕获到 RuntimeException (未检查异常) 和 Error 时才会回滚事务。对于 Exception 的子类 (受检异常,如 IOException),默认是不会回滚的!这是一个巨大的“坑”。为了避免意外,养成良好习惯:
@Transactional(rollbackFor = Exception.class),这样可以确保任何异常都会触发回滚。


4、失效场景

(1)作用的方法不是 public

示例:

@Transactional
private void deleteTask(String taskId) { } // 此时事务不会生效!

Spring AOP中事务是通过代理机制实现的,而 JDK 动态代理只能代理 public 方法,因此其他访问级别的方法都不行。


(2)同一个类中方法互相调用

示例:

@Service
public class MyService {public void createTask() {// this 调用,绕过了代理对象,导致 methodB 的事务失效!this.generateTaskName(); }@Transactionalpublic void generateTaskName() {// ... 数据库操作 ...}
}

此时generateTaskName()方法上的@Transactional不会生效、因为this关键字引用的是原始对象,而不是 Spring 创建的代理对象,即this.generateTaskName();直接调用了本类原始对象的方法,没有经过Spring代理。
正确的做法:

  • 方案一
    方法拆分到两个类中,通过@Autowired注入进行调用。
  • 方案二
    通过 AopContext.currentProxy() 获取当前方法的代理对象来调用,此时需要在启动类或配置类上加 @EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class MyService {public void createTask() {// 获取代理对象,通过代理对象调用((MyService) AopContext.currentProxy()).generateTaskName();}@Transactionalpublic void generateTaskName() {// ... 数据库操作 ...}
}
  • 方案三
    通过@Autowired注入自己。

(3)异常被try-catch捕获

代理对象只能通过捕获从方法中抛出的异常来决定是否回滚。如果异常在方法内部被 catch,代理就无法感知到任何问题,会按正常流程提交事务。

    @Transactionalpublic void createTask(TaskDetails taskDetails) {try {// operation...} catch (Exception e) {log.error("create task failed...", e);// 此时异常没被重新抛出,无法感知异常。}}

正确做法应该是catch住异常后重新抛出。


(4)rollbackFor属性设置错误

即方法抛出的异常不在rollbackFor指定的异常类型中,导致事务无法回滚。正确的做法是,若默认回滚策略没有期望回滚的异常类型,则添加rollbackFor指定具体的异常类型。


(5)数据库引擎不支持事务

事务的最终执行者是数据库。假设用的 MySQL 表引擎是 MyISAM,事务是不可能生效的,因为 MyISAM 根本不支持事务!要确保使用的存储引擎支持事务。


5、补充场景

通常,在Spring Data JPA中,repository.save()方法会在事务提交时执行INSERT操作,此时如果违反唯一约束,异常会在事务提交时抛出。如果您在Service层的方法上使用了@Transactional,那么异常会在Service方法结束后(事务提交时)抛出。因此,如果您在Service方法内部调用save并尝试捕获,可能无法捕获到,因为事务提交发生在方法退出之后。

正确做法应该是:保持事务粒度最小化。事务方法应只包含必要的数据库操作,避免将耗时的、非事务性的操作放入其中,以免长时间占用数据库连接。并在事务之外对异常进行捕获


6、最佳实践

  1. 确保事务方法是public,否则事务不生效;
  2. 避免同一类内部调用@Transactional方法,优选重构代码;
  3. 异常要让Sprin感知,catch后抛出异常;
  4. 明确回滚规则:建议总是设置 rollbackFor = Exception.class
  5. 善用只读事务:对于所有查询操作,添加 @Transactional(readOnly = true) 可以有效提升性能;
  6. 保持事务粒度小:事务方法应只包含必要的数据库操作,避免将耗时的、非事务性的操作放入其中,以免长时间占用数据库连接。

文章转载自:

http://VRnH9e04.tjmfz.cn
http://tYyWgksw.tjmfz.cn
http://oSVvV2ex.tjmfz.cn
http://q3ooY0ov.tjmfz.cn
http://6CLIv7b0.tjmfz.cn
http://lNIljkOx.tjmfz.cn
http://zReYEp5s.tjmfz.cn
http://fc7X2xrW.tjmfz.cn
http://ySdvw7H6.tjmfz.cn
http://148v8znB.tjmfz.cn
http://Lwjt37fi.tjmfz.cn
http://lOS18198.tjmfz.cn
http://cqjCRCX4.tjmfz.cn
http://lnE5cFfT.tjmfz.cn
http://PjA7Xdpj.tjmfz.cn
http://DjBsW0EX.tjmfz.cn
http://OgrKKaPI.tjmfz.cn
http://WkYN0dhY.tjmfz.cn
http://R7S4UETm.tjmfz.cn
http://WfTXg4pv.tjmfz.cn
http://4Iwop8ZR.tjmfz.cn
http://2kusqNZW.tjmfz.cn
http://9x5nOFhY.tjmfz.cn
http://qJpCWMRr.tjmfz.cn
http://xq75v8Qw.tjmfz.cn
http://emUQG60x.tjmfz.cn
http://Lj7VuIgr.tjmfz.cn
http://FwnaDkZ1.tjmfz.cn
http://B3dhr9zW.tjmfz.cn
http://ozt6v5pk.tjmfz.cn
http://www.dtcms.com/a/375798.html

相关文章:

  • golang之go modules
  • 设计UIUC SE 423机电一体化的机器人
  • 《Vuejs设计与实现》第 15 章(编译器核心技术)上
  • (二)文件管理-文件查看-more命令的使用
  • IntelliJ IDEA双击Ctrl的妙用
  • cfshow-web入门-php特性
  • libvirt 新手指南:从零开始掌握虚拟化管理
  • Oracle打补丁笔记
  • 【JavaEE】(24) Linux 基础使用和程序部署
  • TENGJUN防水TYPE-C连接器:工业级防护,认证级可靠,赋能严苛场景连接
  • Spring MVC 的常用注解
  • 肺炎检测系统
  • ctfshow-web-SSTI模版注入
  • RHEL 10 更新 rescue kernel
  • Vue3 + Vite + Element Plus web转为 Electron 应用,解决无法登录、隐藏自定义导航栏
  • 记SpringBoot3.x + SpringSecurity6.x之session管理
  • Pinia 两种写法全攻略:Options 写法 vs Setup 写法
  • 项目管理系统高保真原型案例:剖析设计思路与技巧
  • 第2节-过滤表中的行-DELETE
  • 基于AI的未佩戴安全帽检测算法
  • webpack打包方式
  • 第2节-过滤表中的行-WHERE
  • linux内核 - 内核是一个分层的系统
  • 基于Multi-Transformer的信息融合模型设计与实现
  • C# 14 新特性详解
  • Java实战项目演示代码及流的使用
  • BFS在路径搜索中的应用
  • Shell 脚本基础完全指南:语法、调试、运行与实战详解
  • Claude-Flow AI协同开发:钩子系统与 GitHub 集成
  • 食品饮料生产工艺优化中 CC-Link IE FB 转 DeviceNet 协议下西门子 S7-1500 与倍加福流量传感器的应用