从 @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/>
相关推荐
向前看-1 小时前
验证码机制
前端·后端
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
李小白663 小时前
Spring MVC(上)
java·spring·mvc
AskHarries5 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
Lojarro5 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
isolusion6 小时前
Springboot的创建方式
java·spring boot·后端
Yvemil76 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
zjw_rp6 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder6 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试