Spring Transaction 事务
声明式事务管理
# 事务简介
- 事务是为了保证数据的完整性和一致性而引出的。
- 事务是一组逻辑上紧密关联的多个数据库操作,这些操作要么都执行,要么都不执行。
- 事务的四个属性(ACID)
- 原子性 : 要么都执行,要么都不执行。
- 一致性 : 不论事务多少步操作,保证执行前数据是正确的,执行后是正确的。
- 隔离性 : 多个事务在并发执行过程中不会互相干扰。
- 持久性 : 对数据的修改保存下来。
# Spring 中的事务
可以和脱离 Spring 编程一样,通过原生的 JDBC API 进行事务管理,获取 Connection 对象后提交或者回滚,这种方式是编程式事务管理。 编程式事务管理中,管理的代码和业务代码混编会造成在吗冗余,可以利用上文的 AOP 编程和事务管理结合起来。将事务管理代码从业务中分离出来,这种方式是声明式事务管理。 Spring 在不同的事务管理 API 上定义了一个抽象层,通过配置的方式来管理事务。 事务管理器以普通的 bean 形式声明在 Spring IOC 容器中。
Spring 提供的事务管理器:
- DataSourceTransactionManager : 基于数据源的事务管理,通过 JDBC 存取。
- JtaTransactionManager : 基于 JTA(java Transaction API) 的事务管理。
- HibernateTransactionManager : 用 Hibernate 框架存取的事务管理。
# 初步实现
1.配置文件
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
2
3
4
5
6
7
8
2.在需要事务控制的方法上加上注解 @Transactional
# 事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定,Spring 定义了 7 种类传播行为。
事务的传播属性一个在 @Transactional 注解的 propagation 属性中定义
| 传播属性 | 描述 |
|---|---|
| REQUIRED(默认) | 如果有事务在运行,当前方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。 |
| REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行。如果有事务正在运行,应该将它挂起 |
| SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
| NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
| MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
| NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
| NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 |
@Transactional(
propagation=Propagation.REQUIRED_NEW
)
2
3
说明:
- REQUIRED 传播行为: 如果购物车中有两件商品,其中一件商品是没货的,此时使用
REQUIRED属性,两件上品一件都无法购买。
- REQUIRED_NEW 传播行为: 如果购物车中有两件商品,其中一件商品是没货的,此时使用
REQUIRES_NEW属性,可以成功购买一件。
# 事务的隔离级别
# 数据库事务并发的问题
假设有两个并发的事务 T1 和 T2
脏读- T1 把某条记录的 AGE 属性从 20 改成了 30
- T2 读取了更新后的值: 30
- T1 回滚,把 AGE 的值改成了 20
- T2 读取的 30 就是一个无效值
幻读- T1 读取了表中的一部分数据
- T2 向表中插入了新的行
- T1 读取表中数据,多出了一些行
不可重复读- T1 读取了 AGE 的值为 20
- T2 修改了 AGE 的值为 30
- T1 再次读取 AGE 的值为 30,和第一次读取不一致
# 隔离级别
数据库系统应具有并发隔离运行各个事务的能力,使他们不会相互影响,避免发生各种问题。 一个事务与其他事务隔离的程度被称为隔离级别. 不同的隔离级别拥有不同的干扰程度, 隔离级别越高,数据一致性就越好,但是并发性越弱。
读未提交 READ UNCOMMITTED允许 T1 读取 T2 修改但还未提交的数据读已提交 READ COMMITTED要求 T1 只能读取 T2 已经提交的数据可重复读 REPEATABLE READ确保 T1 可以多次从同一个字段中读取到相同的值,解决不可重复读的问题。即 T1 执行期间禁止其他事务对这个字段进行更新。串行化 SERIALIZABLE确保 T1 可以多次从同一个表中读取到相同的行,解决幻读的问题。即 **T1 执行期间禁止其他事务对这个表进行更新操作,可以避免任何并发问题,但是效率低下。隔离级别解决并发问题对应表
| 脏读 | 不可重复读 | 幻读 | |
|---|---|---|---|
| 读未提交 | 有 | 有 | 有 |
| 读已提交 | 无 | 有 | 有 |
| 可重复读 | 无 | 无 | 有 |
| 串行化 | 无 | 无 | 无 |
MySQL 四种隔离级别都支持,默认为可重复读。Oracle 不支持读未提交
@Transactional(
isolation=Isolation.READ_COMMITTED
)
2
3
# Spring 中指定隔离级别
在 Spring 中用 @Transactional 的 isolation 属性中设置隔离级别.
@Transactional(isolation="READ_COMMITTED")
public void purchase() {}
2
# 触发回滚的异常
事务执行期间默认捕获到 RuntimeException 或者 Error时回滚, 捕获到编译异常时不会滚。 也可以通过属性设置,指定回滚和不回滚的触发异常条件。
// rollbackFor属性: 指定遇到时必须回滚的异常类型
// noRollbackFor属性: 指定遇到时不会滚的异常类型
@Transactional(propagation= Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
rollbackFor=(IOException.class, SQLException.class),
noRollbackFor=(UserNotFoundException.class)
)
public void purchase(String isbn, String username)
2
3
4
5
6
7
8
# 事务的超时和只读属性
超时事务属性:事务在强制回滚之前可以保持多久,可以防止长期运行事务占用资源.只读事务属性:表示事务是读取数据但不更新数据, 可以帮助数据库优化引擎优化事务
@Transactional(
readOnly=true,
timeout=30
)
2
3
4