事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。
Spring 声明式事务的使用
在项目启动类加入@EnableTransactionManagement
开启全局事务,接下来只需要使用@Transactional
进行标注。这个注解可以放在类或者方法上,当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。
在@Transactional
中,还允许配置许多的属性,如事务的隔离级别和传播行为。也是接下来需要重点讲解的地方。
@Transactional的配置项
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 通过bean name指定事务管理器
@AliasFor("transactionManager")
String value() default "";
// 同value属性
@AliasFor("value")
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 {};
}
注意该注解可以放在接口上,也可以放在实现类上。但是Spring推荐放在实现类上,因为放在接口上将使得你的类基于接口的代理时它才生效
Spring事务管理器
在Spring中,事务管理器的顶层接口为PlatformTransactionManager
,Spring还为此定义了一系列的接口和类。
public interface PlatformTransactionManager {
// 获取事务,它还会设置数据属性
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
隔离级别
1. 数据库事务知识
一个数据库事务必须具备四个标准特性:
-
原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
-
一致性(Consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
-
隔离性(Isolation): 即不同事务之间的相互影响和隔离的程度。比如,不同的隔离级别,事务的并发程度也不同,最强的隔离状态是所有的事务都是串行化的。
-
持久性(Durability):一旦事务提交,则其所做的修改不会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。持久性是个有占模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必,而且不可能有能做到100%的持久性保证的策略。)
以上四个特性就是常说的ACID。
2. 丢失更新
第一类丢失更新:
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 读余额为1000 | |
T4 | 取出100,余额改为900 | - |
T5 | 读余额为1000 | |
T6 | 汇入100,余额改为1100 | |
T7 | 提交事务,余额定为1100 | |
T8 | 撤销事务,余额改回1000 | - |
T9 | 最终余额1000,更新丢失 | - |
SQL92没有定义这种现象,标准定义的所有隔离界别都不允许第一类丢失更新发生。
第二类丢失更新:
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 读余额为1000 | |
T4 | 读余额为1000 | |
T5 | 取出100,余额改为900 | |
T6 | 提交事务,余额定为900 | |
T7 | 汇入100,余额改为1100 | - |
T8 | 提交事务,余额定为1100 | - |
T9 | 最终余额1100,更新丢失 | - |
3. 详解隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
我们可以看org.springframework.transaction.annotation.Isolation
枚举类中定义了五个表示隔离级别的值:
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
-
DEFAULT
:这是默认值表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:
READ_COMMITTED
。 -
READ_UNCOMMITTED
:未提交读该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
-
READ_COMMITTED
:读写提交该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
-
REPEATABLE_READ
:可重复读该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
-
SERIALIZABLE
:串行化所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
指定方法:通过使用isolation
属性设置,例如:
@Transactional(isolation = Isolation.DEFAULT)
区分事务隔离级别是为了解决脏读、不可重复读和幻读三个问题的。
事务隔离级别 | 回滚覆盖 | 脏读 | 不可重复读 | 提交覆盖 | 幻读 |
---|---|---|---|---|---|
读未提交 | x | 可能发生 | 可能发生 | 可能发生 | 可能发生 |
读已提交 | x | x | 可能发生 | 可能发生 | 可能发生 |
可重复读 | x | x | x | x | 可能发生 |
串行化 | x | x | x | x | x |
传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
我们可以看org.springframework.transaction.annotation.Propagation
枚举类中定义了6个表示传播行为的枚举值:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED
。如果子方法发生异常,只回滚子方法执行的SQL。
指定方法:通过使用propagation
属性设置,例如:
@Transactional(propagation = Propagation.REQUIRED)
@Transactional 自调失效的问题
例如:
1:在同一个service中嵌套, 如果已经存在外层事务,则nested不会开启新的事务,否则会开启。 nested的savepoint是不起作用的, 内层事务回滚会导致整个事务一同回滚 。
2:在不同的service中嵌套,如果已经存在外层事务,则nested同样不会开启新的事务,否则会开启。
同样,REQUEST_NEW等也会出现类似的问题, 为什么会这样呢?
Spring数据库事务的约定,其实现原理是AOP,而AOP的原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP。
「真诚赞赏,手留余香」
请我喝杯咖啡?
使用微信扫描二维码完成支付
