@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动态代理实现的。
- 解析事务属性 :
当 Spring 容器扫描到有@Transactional
的类或方法时,会使用TransactionAttributeSource
来解析这些注解,得到事务属性。 - 生成代理对象 :
Spring 通过 AOP 生成目标类的代理对象。这个代理对象会包含事务拦截器,并负责管理事务。 - 事务拦截 :
当调用标注了@Transactional
的方法时,事务拦截器(TransactionInterceptor
)会:
-
- 查找并获取当前的事务状态(如果已有事务存在)。
- 根据解析的事务属性决定是开启新事务,还是加入已有事务。
- 执行目标方法。
- 提交或回滚事务:
-
- 如果目标方法执行成功,事务拦截器会提交事务。
- 如果目标方法抛出异常,事务拦截器会回滚事务(根据事务属性配置,可能仅在特定类型的异常时回滚)。
3.3. @Transactional失效
正是由于@Transactional是基于AOP实现的,@Transactional注解会有几个常见的失效情况:
- 自调用:自调用(self-invocation)是指在同一个类的内部,一方法调用另一个标注了
@Transactional
的方法。由于 Spring AOP 使用代理模式创建事务代理,自调用时,并不会触发代理逻辑,从而导致事务失效。 - 非 public 方法:Spring 的事务管理是通过代理对象来实现的,而代理对象只能拦截
public
方法。如果@Transactional
注解被应用在非public
方法上,事务将不会生效。 - 配置不正确:没有启用注解驱动的事务管理,也会导致
@Transactional
失效。需要确保在 Spring 配置类或 XML 配置文件中启用了@EnableTransactionManagement
或<tx:annotation-driven/>
。