You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中@Transactional与@Async引发事务竞态条件异常求助

问题原因分析

当前异常的核心是异步任务与主事务的执行时序不匹配:

  1. 主事务中插入新父记录后,事务尚未提交(Spring事务默认在方法结束时才提交);
  2. @Async标记的updateChild在独立线程中执行,由于数据库默认READ COMMITTED隔离级别,异步线程无法看到主事务未提交的新父记录;
  3. 子表更新时找不到对应的父记录,触发外键约束异常。

移除@Async后,所有操作在同一个事务内,同一事务内的变更可见,因此能正常执行,但同步执行导致性能瓶颈。


解决方案

方案1:利用事务同步回调,提交后触发异步更新

通过TransactionSynchronizationManager注册回调,确保异步任务仅在主事务提交完成后执行,此时新父记录已持久化到数据库。

代码修改:

  1. 在MainRepositoryClass中注入SecondRepositoryClass,并修改determineChildTables方法:
@Repository
public class MainRepositoryClass {

    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    private SecondRepositoryClass secondRepositoryClass; // 新增注入

    // 原updateParentWithCascadetoChildren方法不变

    public void determineChildTables(String parentTableName, ParentObj pObjOld, ParentObj pObjNew){
        List<Child> childMapping = jdbcTemplate.query(<查询受影响子表的SQL>);

        for(Child child : childMapping) {
            // 注册事务提交后的回调
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    // 主事务提交后,触发异步子表更新
                    secondRepositoryClass.updateChild(child, pObjNew);
                }
            });
        }
    }
}
  1. 调整SecondRepositoryClass的事务注解:
    由于异步任务在主事务提交后执行,无需加入主事务,可保留@Async并移除@Transactional(或设置为REQUIRES_NEW保证子更新的原子性):
@Repository
@EnableAsync
public class SecondRepositoryClass {

    @Autowired
    protected JdbcTemplate jdbcTemplate;

    @Async
    // @Transactional(propagation = Propagation.REQUIRES_NEW) // 可选:保证子更新独立事务
    public void updateChild(Child child, ParentObj pObjNew){
        jdbcTemplate.update(<更新子表的SQL>);
    }
}

方案2:基于事务感知事件触发异步更新

利用Spring的TransactionAwareApplicationEventPublisher发布事件,确保事件在主事务提交后被异步监听处理,实现解耦。

代码修改:

  1. 定义子表更新事件类:
public class ChildUpdateEvent extends ApplicationEvent {
    private final Child child;
    private final ParentObj pObjNew;

    public ChildUpdateEvent(Object source, Child child, ParentObj pObjNew) {
        super(source);
        this.child = child;
        this.pObjNew = pObjNew;
    }

    // Getter方法
    public Child getChild() { return child; }
    public ParentObj getPObjNew() { return pObjNew; }
}
  1. 在MainRepositoryClass中注入事务感知事件发布器,修改determineChildTables:
@Repository
public class MainRepositoryClass {

    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    private TransactionAwareApplicationEventPublisher eventPublisher; // 新增注入

    // 原updateParentWithCascadetoChildren方法不变

    public void determineChildTables(String parentTableName, ParentObj pObjOld, ParentObj pObjNew){
        List<Child> childMapping = jdbcTemplate.query(<查询受影响子表的SQL>);

        for(Child child : childMapping) {
            // 发布事务感知事件,主事务提交后才会触发监听
            eventPublisher.publishEvent(new ChildUpdateEvent(this, child, pObjNew));
        }
    }
}
  1. 编写异步事件监听器(替代原SecondRepositoryClass的updateChild方法):
@Component
public class ChildUpdateListener {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Async
    @EventListener
    public void handleChildUpdate(ChildUpdateEvent event) {
        Child child = event.getChild();
        ParentObj pObjNew = event.getPObjNew();
        jdbcTemplate.update(<更新子表的SQL>);
    }
}

注意事项
  • 确保项目中已启用异步支持(通过@EnableAsync注解,可放在启动类或配置类上);
  • 若异步任务需要事务保证,可在异步方法上添加@Transactional(propagation = Propagation.REQUIRES_NEW),避免与其他事务冲突;
  • 避免在事务同步回调中执行过重逻辑,防止阻塞主事务的提交流程。

内容的提问来源于stack exchange,提问作者Timothy Clotworthy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:07:44