透彻的掌握 Spring 中 @transactional 的使用
共 4931字,需浏览 10分钟
·
2021-09-01 11:38
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
推荐:https://www.xttblog.com/?p=5263
透彻的掌握 Spring 中 @transactional 的使用
事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。
声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 「@Transactional
」 注解的方式。注释配置是目前流行的使用方式。
一、@Transactional 注解管理事务的实现步骤
使用 @Transactional 注解管理事务的实现步骤分为两步。
第一步,在 xml 配置文件中添加事务配置信息。
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
第二步,将 @Transactional 注解添加到合适的方法上,并设置合适的属性信息。
@Transactional 注解的属性信息
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback-for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
除此以外,@Transactional 注解也可以添加到类级别上。当把 @Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
方法级别的事务属性信息会覆盖类级别的相关配置信息。
@Transactional 注解的标注于类上:
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
@Service(value ="employeeService")
public class EmployeeService
二、注解方式的事务使用注意事项
当您对 Spring 的基于注解方式的实现步骤和事务内在实现机制有较好的理解之后,就会更好的使用注解方式的事务管理,避免当系统抛出异常,数据不能回滚的问题。
1. 正确的设置@Transactional 的 propagation 属性
本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
2. 正确的设置 @Transactional 的 rollbackFor 属性
默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外的异常,Spring 都不会回滚事务!
如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor。例:
@Transactional(propagation= Propagation.REQUIRED, rollbackFor= MyException.class)
若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
3. @Transactional 只能应用到 public 方法才有效
只有 @Transactional 注解应用到 public 方法,才能进行事务管理。
Spring AOP 会检查目标方法的修饰符是不是 public,若不是 public,就不会获取 @Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。
4. 避免 Spring 的 AOP 的自调用问题
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务被忽略,不会发生回滚。
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。
2. 三大问题
数据库事务的正确执行的 4 个基本要素是 原子性(「A」tommicity)、一致性(「C」onsistency)、隔离性(「I」solation) 和 持久性(「D」urability)。
原子性 : 整个事务中的操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节,仅完成一部分。
一致性 : 指一个事务可以改变数据库状态。食物必须始终保持系统处于一致状态,不管任何给定时间有多少并发事务。
隔离性 : 之两个事务之间的隔离级别。
持久性 : 在事务完成以后,该事务对数据库所做的更改便持久保存在数据库中,并不会被回滚。
三大问题从 严重 到 轻度 以此如下:
「脏读问题」
时刻 | 事务一(老公) | 事务二(老婆) |
---|---|---|
T1 | 查询余额,显示10k | —— |
T2 | —— | 查询余额,显示 10k |
T3 | —— | 网购 1千,显示 9k |
T4 | 请客吃饭开销 1k,显示余额 8k | —— |
T5 | 提交事务 | —— |
T6 | —— | 回滚事务 |
T7 | —— | 最终余额 8k |
所谓脏读,指的就是读到了“脏”数据,即一个事务读取到了另一个事务未提交的数据。
「不可重复读问题」
时刻 | 事务一(老公) | 事务二(老婆) |
---|---|---|
T1 | 查询余额,显示10k | —— |
T2 | —— | 查询余额,显示 10k |
T3 | —— | 网购,开销 1k,余额 9k |
T4 | 请客吃饭,预计开销 2k | —— |
T5 | —— | 网购,开销 8k,余额 1k |
T6 | —— | 提交事务 |
T7 | 吃完买单,显示余额1k,不够付账。 | —— |
不可重复读,指的是理论上的同一条数据,重复读取,居然会不一样,不具备可重复性。
「幻读问题」
时刻 | 事务一(老公) | 事务二(老婆) |
---|---|---|
T1 | —— | 查询信用卡消费记录,显示 10 条记录 |
T2 | 网购 | —— |
T3 | 提交事务 | —— |
T4 | —— | 打印消费记录,有11条记录 |
幻读,和不可重复读类似,第二次读取的数据相较于第一次居然发生了变化,仿佛看到了幻觉。
不可重复读 和 幻读 有一定的相似性,都是指(在本人未改变的情况下)第二次读取的数据,与第一次读取结果不一样。不过它们描述的侧重点(及造成的影响程度)不一样。
不可重复读问题,强调的是某一条数据的内容在“我”两次读取间,发生了改变。(因为 update 语句) 幻读问题,强调的整个数据的数据总量,在“我”两次读取间,发生了改变。(因为 insert / delete 语句) 幻读问题 造成的危害要小于 不可重复读问题。
3. 四个隔离级别
隔离级别表示:「当“我”操作这张表时,“其他人”对这张表还有多大的操作权限」 。“我”的隔离级别越高,其他人的权利就越小,那么“他”要执行他想要执行的操作而没有权限时,那就只能 「等」“我”操作完 。
数据库领域有四个隔离级别(注意,这并非 Java 中特有的概念),针对于上述三大问题,四个隔离级别,从 解决不了任何问题 到 解决所有问题,每一级多解决一个问题。
隔离级别 | 解决问题 | 备注 |
---|---|---|
READ_UNCOMMITTED | 解决不了任何问题 | —— |
READ_COMMITTED | 可以解决脏读 问题 | —— |
REPEATABLE_READ | 可以解决不可重复读 问题 | 包括解决脏读 问题 |
SERIALIZABLE | 解决幻读 问题 | 包括解决不可重复读 和脏读 问题 |
4. 传播机制
传播行为 | 含义 | 备注 |
---|---|---|
REQUIRED | 当方法调用时,如果不存在当前事务,那么就创建事务;如果之前已经存在了事物,那么就沿用之前的事务。 | 默认值 |
SUPPORTS | 当方法调用时,如果不存在当前事务,就不启用事务;如果当前启用事务,那么就沿用当前事务。 | —— |
MANATORY | 方法必须在事务内运行。 | 如果不存在当前事务,则直接抛出异常。 |
REQUIRES_NEW | 无论是否存在当前事务,方法都会在新的事务中运行 | 总是开启一个新事务,执行本方法。 |
NOT_SUPPORTED | 不支持事务,不存在当前事务也不会创建新事务;如果存在当前事务则挂起它,直到方法结束后才恢复当前事务 | 适用于那些不支持事务的数据库和SQL语句 |
NEVER | 不支持事务。 | MANATORY 的“反面”,如果存在当前事务,就直接抛出异常。 |
NESTED | 嵌套事务。REQUIRES_NEW 的高级版 | 支持当前事务中使用保存点(savepoint),可以回滚到保存点;如果当前事务没有保存点,则完全等价于 REQUIRES_NEW |
毫无疑问,最常用的是 REQUIRED,其次是 REQUIRES_NEW 。它们是最常见的业务处理方式,其它方式都是用于处理特定的业务。
传播机制本质上描述的是:在一个整体行为中,一个部分行为的失败,会不会对整体行为造成影响,以及造成何种影响。