Spring事务机制
- 一:故事背景
- 二:核心知识
- 2.1 Spring事务种类
- 2.2.1 编程式事务
- 2.2.2 声明式事务
- 2.2 Spring事务隔离级别
- 2.3 Spring事务传播机制
- 2.3.1 概念
- 2.3.2 七种事务传播机制
- 2.4 Spring声明式事务实现原理
- 2.4.1 Bean初始化创建代理对象
- 2.4.2 执行目标方法时进行事务增强
- 2.5 Spring声明式事务失效场景
- 2.5.1 应用在非Public的方法上
- 2.5.2 属性propagation设置错误
- 2.5.3 rollbackFor设置错误
- 2.5.4 同一个类中方法调用
- 三:总结提升
一:故事背景
本文将重点分享Spring事务相关知识,通过这篇文章,了解Spring事务的实现原理,让你以后在开发中,使用的有底气,有依据。
二:核心知识
2.1 Spring事务种类
2.2.1 编程式事务
- 编程式事务是一种在代码中显式地编写事务管理逻辑的方法,相对于声明式事务来说,更加灵活但也更加繁琐。在编程式事务中,开发者需要通过编写代码来控制事务的开始、提交和回滚。
- Spring框架同样提供了编程式事务管理的支持,通常通过编程式事务管理接口来实现,例如PlatformTransactionManager。
例子:
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;@Component
public class BankService {private PlatformTransactionManager transactionManager;public void setTransactionManager(PlatformTransactionManager transactionManager) {this.transactionManager = transactionManager;}public void transferFunds(String fromAccount, String toAccount, double amount) {TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// 执行转账操作,更新账户余额// 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额updateAccountBalance(fromAccount, -amount);updateAccountBalance(toAccount, amount);transactionManager.commit(txStatus);} catch (Exception e) {transactionManager.rollback(txStatus);throw e;}}private void updateAccountBalance(String account, double amount) {// 更新账户余额的具体实现}
}
2.2.2 声明式事务
我们最常用的就是声明式事务,这里列出3点使用的注意事项:
- 声明式事务使用@Transactional 注解的方式
- 声明式事务的管理是建立在AOP上的,本质是通过AOP功能,对方法的前后进行拦截,将事务的处理编织到拦截的方法中去。
- 通过@Transaction最细的粒度只能做到方法的级别,在目标方法执行前开启事务,执行完成后提交事务,出现错误回滚事务。
例子:
import org.springframework.transaction.annotation.Transactional;@Component
public class BankService {@Transactionalpublic void transferFunds(String fromAccount, String toAccount, double amount) {// 执行转账操作,更新账户余额// 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额updateAccountBalance(fromAccount, -amount);updateAccountBalance(toAccount, amount);}private void updateAccountBalance(String account, double amount) {// 更新账户余额的具体实现}
}
注意如果@Transactional加到方法上的话,会对指定方法进行事务管理、如果加到类上的话,会对这个类所有的public都进行事务管理。
2.2 Spring事务隔离级别
TransactionDefinition 接口定义了事务的各种属性,如传播行为、隔离级别、超时时间等,其实主要还是对应数据库的事务隔离级别。
我们来看一下TransactionDefinition 对应源码
/*** 定义了事务的属性,包括传播行为、隔离级别、超时时间等。*/
public interface TransactionDefinition {int PROPAGATION_REQUIRED = 0; // 当前方法必须在一个事务中执行,如果没有事务则创建一个int PROPAGATION_SUPPORTS = 1; // 当前方法支持在一个事务中执行,如果没有事务也可以int PROPAGATION_MANDATORY = 2; // 当前方法必须在一个事务中执行,如果没有事务则抛出异常int PROPAGATION_REQUIRES_NEW = 3; // 当前方法必须在一个新的事务中执行,如果已存在事务则挂起它int PROPAGATION_NOT_SUPPORTED = 4; // 当前方法不应该在事务中执行,如果存在事务则挂起它int PROPAGATION_NEVER = 5; // 当前方法不应该在事务中执行,如果存在事务则抛出异常int PROPAGATION_NESTED = 6; // 当前方法必须在一个嵌套事务中执行int ISOLATION_DEFAULT = -1; // 使用默认的隔离级别int ISOLATION_READ_UNCOMMITTED = 1; // 读未提交的数据,可能导致脏读、不可重复读、幻读int ISOLATION_READ_COMMITTED = 2; // 读已提交的数据,可以避免脏读,但仍可能有不可重复读、幻读int ISOLATION_REPEATABLE_READ = 4; // 可重复读,可以避免脏读、不可重复读,但仍可能有幻读int ISOLATION_SERIALIZABLE = 8; // 序列化,最高隔离级别,可以避免脏读、不可重复读、幻读int TIMEOUT_DEFAULT = -1; // 使用默认的超时时间/*** 获取事务的传播行为。** @return 事务传播行为*/default int getPropagationBehavior() {return 0;}/*** 获取事务的隔离级别。** @return 事务隔离级别*/default int getIsolationLevel() {return -1;}/*** 获取事务的超时时间。** @return 事务超时时间*/default int getTimeout() {return -1;}/*** 获取事务是否为只读。** @return 是否为只读事务*/default boolean isReadOnly() {return false;}/*** 获取事务的名称。** @return 事务名称*/@Nullabledefault String getName() {return null;}/*** 创建一个具有默认属性的事务定义。** @return 默认的事务定义*/static TransactionDefinition withDefaults() {return StaticTransactionDefinition.INSTANCE;}
}
通过上面的TransactionDefinition类对应的属性可以看出主要还是对应数据库的事务隔离级别
2.3 Spring事务传播机制
2.3.1 概念
Spring 事务的传播机制说的是,当多个事务同时存在的时候— —⼀般指的是多个事务⽅法相互调⽤
时,Spring 如何处理这些事务的⾏为。
事务传播机制是使⽤简单的 ThreadLocal 实现的,所以,如果调⽤的⽅法是在新线程调⽤的,事务传
播实际上是会失效的。
2.3.2 七种事务传播机制
事务传播机制通过指定@Transactional的propagation属性进行指定
@Transactional(propagation = Propagation.REQUIRED)public void test() {}
Propagation枚举里面的7个可选项就是7种对应的传播机制
/*** 定义了事务的传播行为,描述了在多个事务性方法相互调用时,事务的行为方式。*/
public enum Propagation {REQUIRED(0), // 默认 如果当前没有事务,创建一个新事务;如果已存在事务,则加入到当前事务中SUPPORTS(1), // 如果当前存在事务,就在事务中执行;否则以非事务方式执行MANDATORY(2), // 必须在一个已存在的事务中执行,否则抛出异常REQUIRES_NEW(3), // 每次都创建一个新的事务,挂起当前事务(如果存在)NOT_SUPPORTED(4), // 以非事务方式执行,挂起当前事务(如果存在)NEVER(5), // 以非事务方式执行,如果存在事务则抛出异常NESTED(6); // 如果当前存在事务,就在嵌套事务中执行;否则创建一个新事务private final int value;/*** 构造函数,用于设置传播行为对应的数值。** @param value 传播行为的数值表示*/private Propagation(int value) {this.value = value;}/*** 获取传播行为对应的数值表示。** @return 传播行为的数值表示*/public int value() {return this.value;}
}
2.4 Spring声明式事务实现原理
Spring声明式事务的实现原理是通过 AOP+动态代理进行实现的,主要分为以下两个部分:
2.4.1 Bean初始化创建代理对象
Spring 容器在初始化每个单例 bean 的时候:
- 遍历容器中的所有 BeanPostProcessor 实现类,并执⾏其 postProcessAfterInitialization ⽅法
- 在执⾏AbstractAutoProxyCreator 类的 postProcessAfterInitialization ⽅法时会遍历容器中所有的切⾯,查找与当前实例化 bean 匹配的切⾯,这⾥会获取事务属性切⾯,查找@Transactional 注解及其属性值,然后根据得到的切⾯创建⼀个代理对象,默认是使⽤ JDK 动态代理创建代理,如果⽬标类是接口,则使⽤ JDK 动态代理,否则使⽤ Cglib。
2.4.2 执行目标方法时进行事务增强
在执⾏⽬标⽅法时进⾏事务增强操作:当通过代理对象调⽤ Bean ⽅法的时候,会触发对应的
- AOP 增强拦截器,声明式事务是⼀种环绕增强,对应接⼜为 MethodInterceptor ,事务增强对该
接⼜的实现为 TransactionInterceptor。 - 事务拦截器 TransactionInterceptor 在 invoke ⽅法中,通过调⽤⽗类 TransactionAspectSupport
的 invokeWithinTransaction ⽅法进⾏事务处理,包括开启事务、事务提交、异常回滚。
2.5 Spring声明式事务失效场景
2.5.1 应用在非Public的方法上
Spring AOP 代理时,TransactionInterceptor (事务拦截器)在⽬标⽅法执⾏前后进⾏拦
截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept ⽅法 或
JdkDynamicAopProxy 的 invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource
的 computeTransactionAttribute⽅法,获取 Transactional 注解的事务配置信息。
2.5.2 属性propagation设置错误
上面我们讲了7中事务传播行为,如果选择以下三种可能导致事务失效:
2.5.3 rollbackFor设置错误
- rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承⾃ RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
- 若在⽬标⽅法中抛出的异常是 rollbackFor 指定的异常的⼦类,事务同样会回滚。
// 希 望 ⾃ 定 义 的 异 常可以进⾏回滚
@Transactional( propagation = Propagation .REQUIRED , rollbackFor = MyException . class)
2.5.4 同一个类中方法调用
是由于使⽤ Spring AOP 代理,只有当事务⽅法被当前类以外的代码调⽤时,才会由 Spring ⽣成的代理对象来管理。如果我们一个类内有两个方法A和方法B。B开启了事务,A调用了B,A没有开启事务,这样的话,B的事务实际是无效的。
例如:
@Service
public class UserService {@Transactionalpublic void processUser(User user) {// Some processing logic}public void createUserAndProcess(String username) {User user = createUser(username);processUser(user);}
}
如果外部调用的是createUserAndProcess的话processUser的事务就会失效。
三:总结提升
本文系统的总结了Spring中事务的不同种类、隔离级别、传播机制、声明式事务实现原理、还总结了4中可能导致事务失效的场景,希望读者读完之后能明确事务的原理,并且在使用中避开常见的可能导致事务失效的坑。