Spring(二十二) Spring Transaction 事务管理机制
在实际开发中,当我们调用一个基于Spring
的Service
接口方法时,可能会产生服务接口方法的嵌套调用的情况,Spring通过事务传播行为控制为当前的事务如何传播到被嵌套调用的目标服务接口方法中
Spring在TransactionDefinition
接口中规定了7
种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。
Spring 事务机制
Spring 提供了统一的事务处理机制来处理不同的数据访问技术的事务处理。Spring 的事务机制提供了一个 PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现。
Spring 提供了的事务处理有两种使用方式,分别是编程式事务和声明式事务。实际开发大多使用声明式事物,也称为注解式事务,通过在类上或方法上添加@Transaction
注解来开启事务。
事务访问及实现
Spring提供了一个抽象类AbstractPlatformTransactionManager
来实现这个统一的接口,其它具体的数据访问技术的事务处理类继承这个抽象类。
数据访问技术 | 事务实现 |
---|---|
jdbc | DataSourceTransactionManager |
hibernate | HibernateTransactionManager |
jpa | JpaTransactionManager |
jta(分布式事务) | JtaTransactionManager |
定义事务管理器
1 |
|
声明式事务
Spring 支持声明式事务,在类或方法上使用@Transactional
注解来指标当前类下所有的方法或当前方法使用事务。声明式事务是基于AOP
来实现的。**@Transactional来自于 org.springframework.transaction.annotation.Transactional 包。
Spring 提供了一个开启声明式事务的注解@EnableTransactionManagement
,使用该注解后,Spring 容器会自动扫描使用@Transactional**注解的类和方法。
1 | import org.springframework.context.annotation.Configuration; |
编程式事务
使用 TransactionManager
进行编程式事务管理,示例如下。
1 |
|
使用TransactionTemplate
进行编程式事务管理,Spring 推荐方式, 示例如下。
1 |
|
事务失效原因
@Transactional 注解只能应用到
public
可见度的方法上。 如果应用在protected、private
或者 package 可见度的方法上,也不会报错,不过事务设置不会起作用。@Transactional 注解默认只对
Error
和RuntimeException
生效 ,即对unchecked
异常生效。其他继承自 java.lang.Exception 得异常统称为 Checked Exception,如 IOException、TimeoutException 等。例如 读写文件异常,网络异常则不会回滚。
可指定回滚的异常类型,如下:
1
数据库引擎要能支持事务控制。例如,MySQL 的 InnoDB 支持事务,MyISAM 则不支持事务。
是否开启了事务注解解析。
Spring Boot 默认开启了事务自动配置,Spring 需要在 XML 文件配置注解驱动来开启对注解的解析。
添加事务注解的类所在的包是否被 Spring 扫描到,且事务方法是通过 Spring Bean 来调用,本地创建实例调或本地调事务方法是不起效的,例如在一个类里,A方法调B方法,B方法上加事务注解是不生效的。
Spring 的数据库事务是在动态代理进入到一个 invoke 方法里面,然后判断是否需要拦截方法,需要的时候才根据注解或配置生成数据库事务切面上下文,在同一个类 型自调用是没有代理对象的,是原始对象的调用,所以根本就没有 invoke 方法去解析注解和配置生成数据库切面的上下文,独立事务也就无从谈起。
方法内使用
try...catch...
捕获异常且没有抛出,事务则会失效。
Spring 事务扩展
某些场景需要获取到当前的事务及状态进行判断,以做下一项业务。例如,事务提交后发消息,事务提交后回收资源等。
Spring 提供了事务同步管理器 TransactionSynchronizationManager,支持在事务前,事务后等进行一些扩展处理。
获取当前事务
有以下三种方式
TransactionAspectSupport.currentTransactionStatus()
只在 Spring 声明式事务下有效,如果当前无事务会抛异常。编程式事务无效。
1
2TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
transactionStatus.isCompleted();TransactionSynchronizationManager.isSynchronizationActive()
声明式事务 和 编程式事务都有效。返回当前线程是否有活动的事务。
1
TransactionSynchronizationManager.isSynchronizationActive();
TransactionSynchronizationManager.isActualTransactionActive()
声明式事务 和 编程式事务都有效。返回当前线程是否有实际的事务,表明当前线程是否与实际事务相关联,而不仅仅是与活动事务同步相关联。
由资源管理代码调,该代码希望区分活动事务 和 实际活动事务。
SynchronizationActive:活动事务,有或没有支持资源事务,在 PROPAGATION_SUPPORTS 事务传播行为上。
ActualTransactionActive:实际事务,有支持资源事务,在 PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW 等事务传播行为上。
1
TransactionSynchronizationManager.isActualTransactionActive();
增加扩展事项
TransactionSynchronizationManager.registerSynchronization(TransactionSynchronization synchronization) 需要注入一个新的事务同步。
TransactionSynchronization:是个用于事务同步回调的接口。由AbstractPlatformTransactionManager支持。当事务处理处于某个节点时,会调用 TransactionSynchronization 上的对应方法来执行相应的回调逻辑。
该接口从 Spring 5.3 开始方法使用 default 修饰,可直接重写接口需要的方法,TransactionSynchronizationAdapter 已被声明为弃用。
1 | public interface TransactionSynchronization extends Ordered, Flushable { |
示例,在事务完成后发送消息。
1 | if (TransactionSynchronizationManager.isActualTransactionActive()) { |
事务阶段监听器
基于 Spring 的事件发布/监听,Spring 提供了事务的事件监听器来支持对事务行为的扩展。
在业务方法开启了事务,使用 ApplicationEventPublisher 发布事件,监听器使用 @TransactionalEventListener 注解,可以通过 phase
属性指定事务在哪个阶段调用监听器。
事务阶段:
- 提交前:TransactionPhase.BEFORE_COMMIT
- 提交后:TransactionPhase.AFTER_COMMIT
- 回滚后:TransactionPhase.AFTER_ROLLBACK
- 事务完成后:TransactionPhase.AFTER_COMPLETION
Spring 事务控制
事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED = 0 | 如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
PROPAGATION_SUPPORTS = 1 | 支持当前事务。如果当前没有事务,则以非事务的方式执行。 |
PROPAGATION_MANDATORY = 2 | 使用当前的事务。如果当前没有事务,则抛出异常。 |
PROPAGATION_REQUIRES_NEW = 3 | 新建自己的事务。如果当前存在事务,则把当前事务挂起。(必须运行在自己的事务中,直到自己的事务提交或回滚,挂起的事务才恢复执行) |
PROPAGATION_NOT_SUPPORTED = 4 | 以非事务的方式执行操作。如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NEVER = 5 | 以非事务的方式执行。如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED = 6 | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。(如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务) |
在使用PROPAGATION_NESTED
时,底层的数据源必须基于JDBC3.0
,并且实现者需要支持保存点事务机制。
事务隔离级别
事务隔离级别 | 说明 |
---|---|
ISOLATION_DEFAULT = -1 | 由底层数据库决定事务隔离级别作为默认设置 |
ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED = 1 | 可能出现脏读、不可重复读和幻读 |
ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED = 2 | 阻止脏读,可能出现不可重复读和幻读 |
ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ = 4 | 阻止脏读和不可重复读,可能出现幻读 |
ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE = 8 | 不允许脏读、不可重复读和幻读出现。级别最高,开销最大 |
脏读:读到另一个事务未提交的数据。如果另一个事务回滚,则当前事务读到的数据就无效。
不可重复读:在一个事务内多次读到的数据是不一致(原始读取不可重复, 读取数据本身的对比
)。如:t1事务读取了数据, t2事务改变了数据, t1事务检验数据再次读取,两次数据不一致。
幻读:在一个事务内多次读取的结果集不一致(读取数据条数的对比
)。 如:t1修改所有数据, t2又插入了数据, t1再次读取会发现还有数据未被修改,产生幻觉。在 READ_UNCOMMITTED 隔离级别下, 不管事务2的插入操作是否提交, 事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的, 所以READ_UNCOMMITTED 无法避免幻读的问题。
@Transactional属性
propagation:指定事务传播行为, 默认是
Propagation.REQUIRED
。isolation:指定事务隔离级别, 默认是
Isolation.DEFAULT
。readOnly:指定事务是否只读, 默认是
false
。timeout:指定事务过期时间, 默认是
TIMEOUT_DEFAULT = -1
, 即由数据库底层决定事务的过期时间。rollbackFor:指定那些异常要引起事务回滚, 值必须是
Throwable
的子类, 默认是运行时异常和错误异常都会引起事务回滚, 。noRollbackFor:指定那些异常不引起事务回滚, 值必须是
Throwable
的子类。@Transactional源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Transactional {
//默认的事务别名
String value() default "";
//同上
String transactionManager() default "";
//事务传播行为
Propagation propagation() default Propagation.REQUIRED;
//事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
//事务超时
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//只读事务
boolean readOnly() default false;
//指定某些异常来执行事务回滚
Class<? extends Throwable>[] rollbackFor() default {};
//同上
String[] rollbackForClassName() default {};
//指定某些异常不执行事务回滚
Class<? extends Throwable>[] noRollbackFor() default {};
//同上
String[] noRollbackForClassName() default {};
}隔离级别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public enum Isolation {
//默认隔离级别,由底层数据库决定; 其它隔离级别与jdbc的隔离级别对应
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; }
}事务传播行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public enum Propagation {
//支持当前事务, 如果事务不存在则创建, 默认值
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
//支持当前事务, 如果事务不存在则以非事务方式执行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
//支持当前事务, 如果没有事务则抛出异常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
//如果当前已存在事务则暂停, 创建一个新事务
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
//如果当前已存在事务则暂停, 以非事务方式执行
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
//以非事务方式执行, 如果当前已存在事务则抛出异常
NEVER(TransactionDefinition.PROPAGATION_NEVER),
//如果当前已存在事务, 则在嵌套事务中执行(类似于REQUIRED), 注意:实际创建嵌套事务仅适用于特定事务
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
//传入代表传播行为的数字
Propagation(int value) { this.value = value; }
//同上
public int value() { return this.value; }
}
@Transactional使用
- 作用在类上,表示当前类的所有public(被外部调用)方法都开启事务。
- 作用在方法上,表示此方法(public)开启事务。
- 如果在类上和方法上同时使用,则类级别的注解会被方法级别的注解重载, 方法使用的是自己的事务。
Spring JPA 事务
查看JpaRepository
的实现类SimpleJpaRepository
源码,可以看到在类和** save, delete **方法上都添加了注解,开启了事务控制,其它查询事务默认启用readOnly = true
只读属性, 但方法的readOnly
仍为默认的false
。
1 |
|
相关参考
Spring(二十二) Spring Transaction 事务管理机制
http://blog.gxitsky.com/2018/05/30/Spring-22-spring-transaction/