👻 Spring 事务
2022年6月20日
- frame
👻 Spring 事务
- Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的
- Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 binlog | undo log 实现的
1. Spring 事务实现方式
1) 声明式事务
- 通过
xml
|@Transactional(propagation = xxx, isolation = yyy)
实现 - Spring 只允许对 public 方法 上的事务注解起作用,且必须在 代理类外部调用 才行
- 如果直接在目标类里面调用,事务照样不起作用
- 基于 AOP,本质是对方法前后进行拦截
- 在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交 | 回滚事务
- 最细粒度为 方法级别,可作用于 类 | 方法 上
- 实现原理
- 基于 AOP 动态代理 实现
- 在 bean 初始化 阶段创建代理对象
- 在初始化 singleton 时,会执行
BeanPostProcessor
的postProcessBeforeInitialization()
- 在该方法中,会遍历容器中所有切面,查找与当前 bean 匹配的 事务切面
- 找到匹配的 事务切面后,根据
@Transactional(propagation = xxx)
注解及其属性值,创建一个代理对象 - 默认情况下,如果是实现了接口的类,采用 JDK,否则,采用 GCLib
- 在初始化 singleton 时,会执行
- 在执行 目标方法 时,进行 环绕式事务增强 操作
- 当通过 代理对象 调用 bean 方法时,会触发对应的 AOP 增强拦截器
- 进行 开启事务的前置处理 & 事务提交或回滚 的后置处理
- 什么情况下会失效?
- 没有被 Spring 容器管理
- 底层基于 AOP 实现,而 Spring AOP 只能处理 Spring 容器中的 bean
- 注解作用于
非 public
方法上- 源码中若判断目标方法的修饰符是否为
public
,若不满足,直接返回,注解失效
- 源码中若判断目标方法的修饰符是否为
- 事务方法被
当前类的其他方法
调用- 事务方法不能被 事务方法所在类的 其他方法调用,只能被 外部类 调用,若被调用,注解失效
- 数据库不支持事务
- 底层基于 数据库 自身事务,若数据库没有设置事务,则不会有效果
- 注解属性值
propagation = XXX
设置有误- 不同 事务传播行为 有不同的效果,例如
Propagation.NEVER
就要求必须在 非事务 下进行
- 不同 事务传播行为 有不同的效果,例如
- 注解属性值
rollbackFor = XXX
设置有误- Spring 默认情况下,只有出现 Throwable.Exception.RuntimeException 下的某个异常 或
Error
下的错误才会 进行回滚- 其他异常不会触发 回滚操作
- 若设置
rollbackFor = XXX.class
,此时若抛出了指定的异常类,也会执行回滚
- Spring 默认情况下,只有出现 Throwable.Exception.RuntimeException 下的某个异常 或
- 事务方法的
异常被外部方法
抛出- 事务方法内部抛出异常后,若被外部方法
catch
到,则会执行外部的catch
方法,而不是进行 回滚
- 事务方法内部抛出异常后,若被外部方法
- 没有被 Spring 容器管理
- 注解源码
@Target({ElementType.TYPE, ElementType.METHOD}) // 作用于 类 | 方法
@Retention(RetentionPolicy.RUNTIME) // 运行期间有效
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {}; // 事务标签,可用于描述事务
Propagation propagation() default Propagation.REQUIRED; // 事务传播行为,默认 REQUIRED
Isolation isolation() default Isolation.DEFAULT; // 事务隔离级别,默认 与数据库相同
// 事务超时,默认 -1,只在 Propagation.REQUIRED | Propagation.REQUIRES_NEW 下有效
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false; // 若是只读事务,可以设置为 true,默认 false,为 true 时会做一些优化
// 支持定义异常类,这些异常类需要为 Throwable 的子类
// 默认情况下只会回滚 RuntimeException & Error,不会回滚 checked exceptions
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
// 定义不回滚哪些异常类,这些异常类为 Throwable 的子类
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
2) 编程式事务
- 通过
TransactionTemplate
实现 - 最细粒度可以达到 代码块 级别
- 需要写 特定代码 到业务代码中,代码可读性降低,事务代码 & 业务代码 耦合较高
2. 事务隔离级别
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), // 使用后端数据库默认的隔离界别
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), // 读未提交
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), // 读提交
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), // 可重复读
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); // 串行化
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
3. 事务传播行为
- 事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务时如何传播
- 事务传播机制使用
ThreadLocal
实现,因此若调用的方法 是在不同线程
调用的,那么 事务传播行为失效
- 默认事务传播行为为
Propagation.REQUIRED
事务传播行为 | 说明 | 外围事务回滚 | 当前事务回滚 |
---|---|---|---|
Propagation.REQUIRED | 没有事务,则新建一个事务;有事务,则支持这个事务中 | 由于加入到外围事务,因此为同一个事务,当其事务回滚 | 同属一个事务,外围事务回滚 |
Propagation.REQUIRED_NEW | 没有事务,则新建一个事务;有事务,则挂起那个事务 | 外围事务与本事务相互独立,当前事务不回滚 | 相互独立,外围事务不回滚 |
Propagation.NESTED | 没有事务,则新建一个事务;有事务,有事务,则在嵌套事务内进行 | 属于外围事务子事务,当前事务回滚 | 外围事务子事务,外围不回滚 |
Propagation.SUPPORTS | 没有事务,则以非事务方式进行;有事务,则支持当前事务 | ||
Propagation.MANDATORY | 没有事务,则抛出异常;有事务,则使用当前事务 | ||
Propagation.NOT_SUPPORTED | 没有事务,则以非事务方式进行;有事务,则把当前事务挂起 | ||
Propagation.NEVER | 没有事务,则以非事务方式进行;有事务,则抛出异常 |