前言
在开发过程中,出现了一个bug,在一个事务方法中调用数据新增,然后再进行查询查不到。于是我重温了一下spring中的事务传播。
代码复现
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void mainFun(Order order) {
this.insertFun();
this.searchFun();
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void insertFun(String name) {
//插入方法
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void searchFun(String name) {
//查询方法
}
新增的方法传播行为是REQUIRES_NEW,所以肯定是已经提交数据了。数据库已经查到
然后再查询数据库的默认隔离级别后豁然开朗。
select @@transaction_isolation; -- 查询隔离级别
隔离级别为可重复读,所以在同一个事务中,查询的数据一定是这个事务所发生的事情。可以修改对应方法的隔离级别为读已提交
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void searchFun(String nameo) {
//查询方法
}
传播行为
在 Spring 框架中,@Transactional
注解的 传播级别(传播行为) 用于定义多个事务方法相互调用时,事务如何在这些方法间传播,解决 “方法嵌套调用时,事务怎么协同工作” 问题。常见传播级别及适用场景如下:
1. PROPAGATION_REQUIRED
(默认级别)
行为:若当前上下文已有事务,就加入该事务;若没有,新建事务执行。
适用场景:大多数常规业务场景,尤其是需要 “多个操作属于同一事务,要么全成功、要么全回滚” 的情况。
示例:电商下单,需扣库存、生成订单、减优惠券,这些操作若用不同方法实现,标记为
REQUIRED
可保证它们在一个事务里,某一步失败则整体回滚。
2. PROPAGATION_SUPPORTS
行为:若当前有事务,就加入;若没有,以非事务方式执行(即操作不被事务包裹,执行后直接提交,出错仅当前操作回滚,不影响外层)。
适用场景:非核心、非原子性的辅助逻辑,允许 “有事务就参与,没事务也能单独执行”。
示例:订单系统里,“记录用户操作日志” 的方法。若主流程(如下单)有事务,日志操作就加入;若单独调用日志方法(如后台手动触发),也能非事务执行。
3. PROPAGATION_MANDATORY
行为:必须在事务中执行!若当前有事务,加入;若没有,直接抛异常(强制要求调用方有事务上下文)。
适用场景:严格依赖事务的操作,确保方法只能被事务性方法调用,避免 “非事务调用导致数据不一致”。
示例:财务系统中 “资金扣减” 的核心方法,必须被事务包裹(如订单支付流程),若被非事务代码调用就抛异常,防止漏事务导致资金异常。
4. PROPAGATION_REQUIRES_NEW
行为:无论当前是否有事务,新建独立事务,并挂起当前上下文的事务(若有)。新事务执行完,再恢复原事务。
适用场景:需要 “独立事务,与外层事务隔离” 的操作,即便外层事务回滚,自身也能提交;或自身失败不影响外层(但需手动处理异常)。
示例 1:“发送红包” 场景,发红包前初始化、验证(外层事务),发红包(
REQUIRES_NEW
,保证发红包操作独立,即便后续记录日志失败,发红包结果也不回滚 ),最后记录日志(回滚不影响发红包)。示例 2:用户注册时,“创建用户”(主事务) + “发送注册成功短信”(
REQUIRES_NEW
,短信发送失败不回滚用户创建 )。
5. PROPAGATION_NOT_SUPPORTED
行为:以非事务方式执行,若当前有事务,就挂起该事务。
适用场景:明确不需要事务的操作,常用于 “执行耗时、只读且不影响数据一致性” 的逻辑,减少事务开销。
示例:报表统计接口,纯查询数据库,用
NOT_SUPPORTED
移除事务,避免事务资源占用。
6. PROPAGATION_NEVER
行为:必须以非事务方式执行!若当前有事务,直接抛异常。
适用场景:严格禁止在事务中执行的操作,确保方法不会被事务性代码调用,防止因事务导致逻辑异常。
示例:某些 “纯内存计算、不涉及数据库” 的工具方法,若被事务方法误调用,抛异常提示调用方调整。
7. PROPAGATION_NESTED
行为:若当前有事务,创建嵌套事务(基于 savepoint 实现,类似 “事务内的子事务” );若没有,等价于
REQUIRED
(新建事务 )。嵌套事务回滚不会影响外层事务(仅回滚到 savepoint ),但外层事务回滚会带动嵌套事务回滚。适用场景:需要 “部分回滚” 的批量操作,性能比
REQUIRES_NEW
好(无需频繁创建新事务)。示例:批量导入数据,循环调用 “单条数据导入” 方法(标记为
NESTED
)。某条数据导入失败,仅回滚这条数据的操作,不影响其他数据和外层事务,最后统一提交(或外层决定是否整体提交 )。
简单总结:
常规业务用
REQUIRED
;辅助逻辑、可灵活适配事务有无用
SUPPORTS
;严格依赖事务用
MANDATORY
;需完全独立事务用
REQUIRES_NEW
;纯查询、非事务逻辑用
NOT_SUPPORTED
;禁止事务调用用
NEVER
;批量操作需部分回滚用
NESTED
。
实际开发要结合业务对 “事务协同、回滚范围” 的需求,选对应传播级别