Spring 事务和事务传播机制
一、Spring 事务的基本概念
事务是一组操作,被视为一个不可分割的工作单元,要么全部完成,要么全部失败回滚,以此来确保数据的一致性和完整性。Spring事务管理允许我们在应用程序中声明式地或编程式地管理事务,它提供了一个事务管理抽象层,使得事务的使用和配置更加简单和灵活。
Spring事务管理不直接管理数据库事务,而是通过委托给底层的数据库事务管理器,如JDBC或Hibernate的事务管理器,来实现对数据库事务的控制。Spring事务管理分为编码式和声明式的两种方式。
- 编程式事务:通过编码方式实现事务。
- 声明式事务:基于AOP,即使用@Transactional注解,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染,因此在实际使用中声明式事务用的比较多。
通过简单的配置或注解,Spring允许开发者将事务管理从业务代码中分离出来,提高了代码的可读性和可维护性。使用@Transactional注解,简单配置即可管理事务。例如,在Service层的方法上标注@Transactional注解,Spring将自动处理事务边界。
使用TransactionTemplate或PlatformTransactionManager手动管理事务,虽然不常用,但在某些情况下,可以用于需要更细粒度控制的场景。
值得注意的是,Spring利用AOP和事务拦截器来拦截被@Transactional注解的方法,在方法调用前开启事务,在方法调用后根据方法执行结果决定提交或回滚事务。
此外,在某些情况下,@Transactional注解可能会失效,包括:
-
方法是非public的,因为事务的底层是利用cglib代理实现,cglib是基于父子类来实现的,子类是不能重载父类的private方法,所以无法很好利用代理。
-
使用的数据库引擎不支持事务,因为Spring的事务调用的也是数据库事务的API,如果数据库都不支持事务,那么@Transactional注解也就失效了。
-
添加了@Transactional注解的方法不能在同一个类中调用,否则会使事务失效。这是因为Spring AOP通过代理来管理事务,自调用不会经过代理。
-
@Transactional注解属性propagation设置错误,若是错误的配置以下三种propagation,事务将不会发生回滚:
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
-
@Transactional注解属性rollbackFor设置错误,rollbackFor可以指定能够触发事务回滚的异常类型。默认情况下,Spring仅在抛出未检查异常(继承自RuntimeException)时回滚事务。对于受检异常(继承自Exception),事务不会回滚,除非明确配置了rollbackFor属性。
-
异常被捕获了,导致@Transactional失效。当事务方法中抛出一个异常后,应该是需要表示当前事务需要rollback,如果在事务方法中手动捕获了该异常,那么事务方法则会认为当前事务应该正常提交,此时就会出现事务方法中明明有报错信息表示当前事务需要回滚,但是事务方法认为是正常,出现了前后不一致,也会因此抛出UnexpectedRollbackException异常。
二、Spring 事务的传播机制
事务的传播机制是Spring事务管理中的核心概念之一。在多个包含事务的方法里相互调用时,它们之间是如何扩散或者传递的,就是事务的传播机制。这个机制保证了一个事务在多个调用方法之间的可控性(稳定性)。
Spring框架中,事务传播机制是指在一个事务方法调用另一个事务方法时,Spring如何管理这些方法之间的事务边界。Spring提供了七种事务传播行为,以满足不同的业务需求。
-
REQUIRED:这是最常用的事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这种行为确保所有事务操作都在一个统一的事务上下文中执行。适用场景是大多数情况下使用此传播行为,确保多个事务操作在同一个事务中执行,以保持数据一致性。
如果外层方法没有事务管理,那么调用事务方法的外层方法没有被事务注解(如@Transactional)标记,在调用事务方法时,当前就没有事务存在。例如,如果方法A没有事务管理,方法B被标记为@Transactional,当调用方法A时,方法A没有事务管理,因此当前没有事务。当方法A调用方法B时,由于方法A没有事务,所以方法B会创建一个新的事务。如果将方法A也标记为@Transactional,那么方法A开启了一个事务,因此当前有事务。当方法A调用方法B时,方法B会加入方法A的事务,而不是创建新的事务。
-
SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。这种传播行为适用于既能在事务内执行,也能在事务外执行的操作。比如,某些只读操作或性能要求不高的操作。
-
MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这种传播行为适用于必须在现有事务中执行的操作。通常用于一些关键业务逻辑,要求调用者必须在事务中运行。
-
REQUIRES_NEW:总是启动一个新的事务。如果当前存在事务,则将当前事务挂起。在新的事务执行完成后,恢复先前的事务。这种传播行为适用于需要独立事务的情况,例如记录日志或审计信息,不希望这些操作受到主事务的影响。
-
NOT_SUPPORTED:以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。这种传播行为适用于不需要事务支持的操作,或某些性能要求高、不希望受到事务管理开销影响的操作。
-
NEVER:以非事务方式执行。如果当前存在事务,则抛出异常。这种传播行为适用于明确不希望在事务中执行的操作。比如,某些数据库操作可能不支持事务。
-
NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前不存在事务,则等价于REQUIRED,即创建一个新的事务。嵌套事务依赖于底层数据库对保存点(savepoint)的支持。这种传播行为适用于需要部分提交或回滚的复杂业务操作。比如,复杂的财务操作,某些步骤失败后需要回滚到特定点。
下面通过一个具体的例子来说明事务传播机制的作用:
java
public class MyService {
public void methodA() {
// 非事务代码
methodB(); // 调用有事务管理的方法
// 继续执行非事务代码
}
@Transactional
public void methodB() {
// 有事务管理的代码
// 执行数据库操作
}
}
在这个例子中,methodA开始执行时,此时没有事务。当methodA调用methodB时,由于methodB被标记为@Transactional,Spring会为methodB创建一个新的事务。methodB在事务中执行其代码(如数据库操作)。methodB执行完成后,事务提交或回滚(取决于方法执行的结果和异常情况)。methodB返回到methodA,此时事务已经结束。methodA继续执行剩余代码,但此时已经没有事务。所以,在methodA中,methodB执行的部分是在事务中的,但methodB返回后,methodA剩余的代码是不在事务中的。事务边界由methodB的开始和结束决定,事务结束后事务上下文不再存在。
三、事务传播机制的组合和策略
在实际应用中,可能会遇到多种事务传播行为的组合使用。理解这些组合的行为对于正确设计事务管理策略至关重要。
- REQUIRED-REQUIRED:如果A方法在事务中运行,则B方法将继承A方法的事务。如果A方法没有事务,则B方法将在一个新的事务中运行。
- REQUIRED-SUPPORTS:如果A方法在事务中运行,则B方法将继承A方法的事务。如果A方法没有事务,则B方法将以非事务方式运行。
- REQUIRED-MANDATORY:如果A方法在事务中运行,则B方法将继承A方法的事务。如果A方法没有事务,则B方法将抛出异常。
- REQUIRED-REQUIRES_NEW:无论A方法是否在事务中运行,B方法都会开启一个新的事务并在该事务中运行。
- REQUIRED-NOT_SUPPORTED:如果A方法在事务中运行,则B方法将以非事务方式运行。如果A方法没有事务,则B方法也将以非事务方式运行。
- REQUIRED-NEVER:如果A方法在事务中运行,则B方法将抛出异常。如果A方法没有事务,则B方法将以非事务方式运行。
- REQUIRED-NESTED:如果A方法在事务中运行,则B方法将作为A方法的嵌套事务运行。如果A方法没有事务,则B方法将在一个新的事务中运行。
类似的,还可以根据其他传播行为组合出更多的策略。在实际应用中,需要根据具体的业务需求来选择合适的事务传播行为组合。
四、事务隔离级别
事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度。当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
- 脏读:一个事务读取了另一个事务未提交的更改。
- 不可重复读:在同一个事务中,两次读取同一数据的结果不一致。
- 幻读:一个事务读取了另一个事务插入的数据。
Spring事务管理框架支持标准的数据库事务隔离级别,这些隔离级别与底层数据库系统所支持的