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