Spring 事务@Transactional
事务 @Transactional
可设置多个属性来控制事务的行为:
-
propagation
:事务传播行为 -
isolation
:隔离级别 -
timeout
:超时设置(单位:秒) -
readOnly
:事务是否只读。如果设置为 true,Spring 会优化事务的执行,比如不会进行脏读。 -
rollbackFor
:哪些异常会导致事务回滚。默认情况下,运行时异常会导致事务回滚,而检查型异常不会。 -
noRollbackFor
:哪些异常不会导致事务回滚。
propagation 事务传播行为
事务传播行为 Propagation
定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。以下是 propagation
枚举的详细说明:
传播行为 | 常量 | 描述 |
---|---|---|
当前存在事务,加入该事务 | REQUIRED | 如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务(默认项) |
当前存在事务,以非事务方式运行 | SUPPORTS | 如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。 |
当前存在事务,必须在该事务中运行 | MANDATORY | 如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常。 |
创建一个新事务,挂起当前事务 | REQUIRES_NEW | 总是创建一个新的事务。如果当前存在事务,则将当前事务挂起。当自己执行完成后(提交或回滚),再接着执行被挂起的事务 |
以非事务方式运行,如果当前存在事务,则挂起 | NOT_SUPPORTED | 以非事务方式运行。如果当前存在事务,则挂起当前事务。 |
以非事务方式运行,如果当前存在事务,则抛出异常 | NEVER | 以非事务方式运行。如果当前存在事务,则抛出异常。 |
在嵌套事务中运行,如果当前没有事务,则创建 | NESTED | 如果当前存在事务,则在嵌套事务中运行。如果当前没有事务,则创建一个新的事务。 |
正确理解和使用事务传播行为对于确保事务的一致性和正确性至关重要。
isolation 隔离级别
隔离级别是事务管理的一个关键属性,它定义了一个事务在并发环境下如何与其他事务隔离。隔离级别决定了当前事务在并发环境下如何看到其他并发事务所做的修改。隔离级别越高,数据一致性越好,但并发性能可能会降低。反之,隔离级别越低,并发性能越好,但数据一致性风险增加;以下是 isolation
枚举的详细说明:
隔离级别 | 常量 | 描述 |
---|---|---|
默认项 | DEFAULT | 默认,事务将使用底层数据库的默认隔离级别 |
读未提交 | READ_UNCOMMITTED | 最低级别的隔离,允许一个事务读取到其他事务尚未提交的数据,存在脏读(Dirty Read)问题,即读取到其他事务未提交的数据 |
读已提交 | READ_COMMITTED | 当前事务只能读取到其他事务已经提交的数据,解决了脏读问题,但仍然存在不可重复读(Non-Repeatable Read)问题,即在同一事务中多次读取同一数据集的结果可能不同 |
可重复读 | REPEATABLE_READ | 保证在同一个事务中多次读取同一数据集的结果是一致的,解决了不可重复读问题,但可能存在幻读(Phantom Read)问题,即一个事务在读取某个范围内的记录时,另一个事务插入或删除记录,导致第一个事务再次读取时返回不同的记录数 |
串行化 | SERIALIZABLE | 最高级别的隔离,通过锁定涉及的所有数据来确保事务序列化执行,从而避免了脏读、不可重复读和幻读,解决了所有并发问题,但性能成本最高,因为会显著降低并发性能 |
timeout 事务超时
限定事务在指定时间内完成。超时事务回滚。时间单位s。默认值-1,即事务将可以无限期地运行直到完成。
readOnly 事务是否只读
bool类型,默认 false
,声明事务是否为只读事务,设置为 true 时,该事务只涉及读操作,不会进行修改数据库操作。对于只读事务,Spring 会进行一些优化,因为它知道该事务不更改数据,因此减少锁的持有时间,提高并发性能
只读事务常用于以下情况:
- 查询操作:比如统计、数据导出等只涉及到查询数据库的操作。
- 某些特定的更新操作:如果更新操作只涉及到更新一些不需要事务支持的字段(如:更新一些不需要保证原子性的字段),也可设置为只读。
当 readOnly 属性设置为 false(默认值)时,表示该事务可能会进行读和写操作。在这种情况下,Spring 不会应用只读事务的优化
rollbackFor 异常回滚
Exception 数组,默认{ RuntimeException.class }
;指定哪些异常类型会触发事务回滚。通过设置 rollbackFor 属性,可以扩展触发回滚的异常类型,包括已检查异常或其他异常类型
noRollbackFor 异常不回滚,继续执行
Exception 数组,无默认值,用于指定哪些异常类型不会触发事务回滚。通过设置 noRollbackFor 属性,可以排除某些异常类型,使得即使这些方法抛出这些异常,事务也不会回滚
在 Spring 中,默认情况下:
- 当遇到 运行时异常(RuntimeException)或其子类时,Spring 事务管理器会触发事务回滚。
- 当遇到 已检查异常(即非运行时异常,继承自 Exception 但不继承自 RuntimeException)时,Spring 事务管理器默认不会触发事务回滚
@Transactional 使用
- 一个方法在类中调用另一个方法,并且被调用的方法没有自己的事务注解(如下所示),那么默认情况下,被调用方法中的数据库操作不会加入调用者方法的事务中,被调用方法中的数据库操作将在它们自己的独立事务中执行,调用者的事务不会自动传播到被调用者
@Servicepublic class SaveService {@Transactionalpublic void funA() {this.funB();// 数据库操作this.funC();}public void funB() {// 数据库操作}public void funC() {// 数据库操作}}
- 为了保持数据的一致性,建议被调用方法加上
@Transactional
- 事务传播行为只适用于被
@Transactional
注解的方法 @Transactional
属性设置仅对当前所注解的方法起效,并不对方法内部调用的其他方法起效public class MyService {@Transactionalpublic void funA() {// 数据库操作(仅对这里的数据操作起效,并不对funB()起效)this.funB();}public void funB() {// 数据库操作} }
- 对事务属性的设置也会影响事务的执行。
// funA 方法被调用,它会在自己的事务中执行,且 funB 和 funC 方法会加入这个事务。// funC 或 funB 被单独调用,不会创建新事务,而是会在没有事务的上下文中执行@Servicepublic class MyService {@Transactionalpublic void funA() {this.funB();this.funC();}@Transactional(propagation = Propagation.SUPPORTS)public void funB() {// 数据库操作}@Transactional(propagation = Propagation.SUPPORTS)public void funC() {// 数据库操作}}
- 事务传播行为由被调用方的方法决定,而不是调用方的方法
- 嵌套事务(
NESTED
)需要数据库支持嵌套事务虽然 MySQL 不支持真正意义上的 嵌套事务,但使用
Propagation.REQUIRES_NEW
仍然是可行的,因为它会启动一个新的事务,并且这个新事务与外部事务是隔离的。如果新事务成功,它会被提交,即使外部事务最终失败也是如此。如果新事务失败,它会被回滚,并且不影响外部事务 - 使用
REQUIRES_NEW
时,确保理解它会导致当前事务被挂起,并且新事务的异常不会导致当前事务回滚
拓展
@Transactional 仅能适用于单机数据库保持数据一致性,面对多节点多应用的分布式场景下,可使用
Alibaba SETA
框架实现分布式事务