从 @Transactional聊一聊事务那些事

@Transactional是spring原声的事务管理注解,今天我们以它为切入口,聊一聊代码中的事务问题。

1. 事务问题

服务端的事务问题来源于DB,与数据库的事务密切相关:

  • 原子性 (Atomicity) :原子性确保事务要么被全部执行,要么完全不执行。如果事务中某一步操作失败,整个事务的所有操作都将被撤销,数据库将返回到执行该事务之前的状态。
  • 一致性 (Consistency) :一致性确保数据库从一个有效状态转换到另一个有效状态。事务在执行前和执行后都必须保持数据库的约束规则和完整性约束。
  • 隔离性 (Isolation) ::隔离性确保并发事务的执行不会相互干扰,一个事务的操作在最终提交以前对此事务之外是不可见的。DBMS 通常通过锁机制来实现隔离性。
  • 持久性 (Durability) :持久性确保一旦事务提交,其结果对数据库的影响是持久的,即使数据库系统遇到故障(例如,系统崩溃),数据仍然存在。

@Transactional注解保障的就是数据库增删改操作的事务,是需要依赖DB的事务能力的,因此,本身不支持事务操作的数据库或中间件,@Transactional注解也是无法保障事务的。

@Transactional可以用于类、接口与方法,官方是不推荐@Transactional用于接口的,因为AOP操作会导致接口的@Transactional失效,这一点后面说,如果@Transactional同时作用于类以及其中的一个方法,那么配置信息以方法的为准。

2. @Transactional注解的参数

2.1. 事务传播行为 propagation

propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,各参数解释如下:

  • REQUIRED:如果当前没有事务,则创建一个新的事务;如果已经存在一个事务,则加入当前事务。
  • REQUIRES_NEW:每次都创建一个新的事务,如果当前已经有一个事务,则挂起当前事务。挂起当前事务的意思是,当前事务的执行暂停,待新事务完成后再恢复执行。新事务提交后,主事务回滚不会影响这个新事务。
  • NESTED:如果当前有事务,则在当前事务中嵌套一个事务,若外部事务回滚,则嵌套事务也会回滚;如果当前没有事务,则创建一个新的事务。
  • MANDATORY:必须在一个现有事务中运行,如果当前没有事务,则抛出异常。
  • NEVER:不能在事务中运行,如果当前有事务,则抛出异常。
  • NOT_SUPPORTED:当前方法不支持事务,如果当前有事务,则将事务挂起。
  • SUPPORTS:如果当前有事务,则加入该事务;如果当前没有事务,也可以非事务方式运行。

需要注意的是,事务的传播行为是针对被调用方来说的,比如方法a调用了方法b,方法b的propagation为Propagation.REQUIRED,那么方法b就会和方法a合并成一个事务;如果方法b的propagation为Propagation.REQUIRES_NEW,那么方法b的事务和方法a就是合理的,不管是方法a还是方法b失败,都不会对各自产生影响。

2.2. 事务隔离级别 isolation

和数据库的隔离级别是完全一致的,默认为Isolation.DEFAULT

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
  • Isolation.READ_UNCOMMITTED:读未提交
  • Isolation.READ_COMMITTED:读已提交
  • Isolation.REPEATABLE_READ:可重复读
  • Isolation.SERIALIZABLE:串行化

2.3. 超时时间 timeout

事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

2.4. readOnly

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

2.5. (no)rollbackFor

  • rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
  • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

3. @Transactional与AOP

3.1. AOP

Spring 的 AOP(面向切面编程,Aspect-Oriented Programming)是一种编程范式,它旨在将横切关注点(cross-cutting concerns,如日志记录、安全性、事务管理等)与业务逻辑分离,从而提高代码的模块化和可维护性。

Spring AOP(Aspect-Oriented Programming)的实现主要依赖于代理模式。Spring AOP 使用两种代理机制来实现在目标对象方法之前、之后或环绕目标方法进行额外操作的功能:JDK 动态代理和 CGLIB(Code Generation Library)代理。

JDK 动态代理用于实现接口的代理。它在运行时生成代理类,该代理类实现了一个或多个接口,并将方法调用动态地分派给一个 InvocationHandler 实例。Spring AOP 使用 JDK 动态代理来代理实现了接口的 Bean。

CGLIB 代理用于对没有实现接口的类进行代理。它通过生成目标类的子类,并在子类中拦截对方法的调用来实现代理功能。Spring AOP 使用 CGLIB 来代理没有实现接口的 Bean。

3.2. @Transactional原理

@Transactional也是依靠AOP动态代理实现的。

  1. 解析事务属性
    当 Spring 容器扫描到有 @Transactional 的类或方法时,会使用 TransactionAttributeSource 来解析这些注解,得到事务属性。
  2. 生成代理对象
    Spring 通过 AOP 生成目标类的代理对象。这个代理对象会包含事务拦截器,并负责管理事务。
  3. 事务拦截
    当调用标注了 @Transactional 的方法时,事务拦截器(TransactionInterceptor)会:
    • 查找并获取当前的事务状态(如果已有事务存在)。
    • 根据解析的事务属性决定是开启新事务,还是加入已有事务。
    • 执行目标方法。
  1. 提交或回滚事务
    • 如果目标方法执行成功,事务拦截器会提交事务。
    • 如果目标方法抛出异常,事务拦截器会回滚事务(根据事务属性配置,可能仅在特定类型的异常时回滚)。

3.3. @Transactional失效

正是由于@Transactional是基于AOP实现的,@Transactional注解会有几个常见的失效情况:

  • 自调用:自调用(self-invocation)是指在同一个类的内部,一方法调用另一个标注了 @Transactional 的方法。由于 Spring AOP 使用代理模式创建事务代理,自调用时,并不会触发代理逻辑,从而导致事务失效。
  • 非 public 方法:Spring 的事务管理是通过代理对象来实现的,而代理对象只能拦截 public 方法。如果 @Transactional 注解被应用在非 public 方法上,事务将不会生效。
  • 配置不正确:没有启用注解驱动的事务管理,也会导致 @Transactional 失效。需要确保在 Spring 配置类或 XML 配置文件中启用了 @EnableTransactionManagement<tx:annotation-driven/>
相关推荐
梦飞翔2382 小时前
Spring Boot
spring boot
青柠编程2 小时前
基于Spring Boot的选课管理系统架构设计
java·spring boot·后端
前端橙一陈2 小时前
LocalStorage Token vs HttpOnly Cookie 认证方案
前端·spring boot
RainbowSea3 小时前
9. Spring AI 当中对应 MCP 的操作
java·spring·ai编程
RainbowSea3 小时前
10. Spring AI + RAG
java·spring·ai编程
每次的天空4 小时前
Android -Glide实战技术总结
java·spring boot·spring
码事漫谈5 小时前
C++内存泄漏排查:从基础到高级的完整工具指南
后端
王嘉俊9255 小时前
ThinkPHP 入门:快速构建 PHP Web 应用的强大框架
开发语言·前端·后端·php·框架·thinkphp
码事漫谈5 小时前
C++多线程数据竞争:从检测到修复的完整指南
后端
Code blocks6 小时前
SpringBoot快速生成二维码
java·spring boot·后端