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 {
@Transactional
public 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 事务名称
*/
@Nullable
default 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 {
@Transactional
public void processUser(User user) {
// Some processing logic
}
public void createUserAndProcess(String username) {
User user = createUser(username);
processUser(user);
}
}
如果外部调用的是createUserAndProcess的话processUser的事务就会失效。
三:总结提升
本文系统的总结了Spring中事务的不同种类、隔离级别、传播机制、声明式事务实现原理、还总结了4中可能导致事务失效的场景,希望读者读完之后能明确事务的原理,并且在使用中避开常见的可能导致事务失效的坑。