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

捕获UniqueConstraintViolationException后事务仍回滚,求解决方案

问题原因分析

核心问题:事务回滚标记无法清除

  1. 事务传播共享上下文:m2方法的@Transactional默认传播行为是REQUIRED,会直接加入m1开启的同一事务上下文。第一次抛出UniqueConstraintViolationException时,Spring事务管理器会立刻标记当前事务为需回滚状态——哪怕你在m2内部捕获了异常,这个回滚标记也不会被清除。
  2. 回滚标记不可逆:后续重试保存成功后,m1执行完毕时Spring仍会按照回滚标记,将整个事务(包括m3的写入操作)全部回滚,最终返回500错误。
解决方案

方案一:给m2设置独立事务(推荐)

将m2的事务传播属性改为REQUIRES_NEW,让每次重试都在独立事务中执行,不会影响m1的主事务:

@Transactional(propagation = Propagation.REQUIRES_NEW)
void m2(Event event){
    boolean success = false;
    while(!success){
        try{
            eventRepo.save(event);
            success = true;
        } catch(UniqueConstraintViolationException e){
            event.setVersion(++event.getVersion());
        }
    }
}
  • 原理:REQUIRES_NEW会暂停主事务,开启全新的独立事务,每次重试的保存操作都在新事务中完成,成功后立即提交,不受主事务的回滚标记影响。
  • 效果:m2的操作提交后,m1的主事务可以正常执行m3的写入,最终整体提交。

方案二:调整异常类型避免触发回滚

Spring默认仅对RuntimeException和Error触发事务回滚,可将异常转换为受检异常,避免事务管理器标记回滚:

@Transactional
void m2(Event event){
    boolean success = false;
    while(!success){
        try{
            eventRepo.save(event);
            success = true;
        } catch(UniqueConstraintViolationException e){
            event.setVersion(++event.getVersion());
            // 包装为受检异常,避免触发事务回滚
            throw new CheckedVersionConflictException("版本冲突,重试中", e);
        }
    }
}

// 自定义受检异常
class CheckedVersionConflictException extends Exception {
    public CheckedVersionConflictException(String message, Throwable cause) {
        super(message, cause);
    }
}

同时在m1中捕获该受检异常:

@Transactional
void m1(Event event){
    try {
        m2(event);
    } catch (CheckedVersionConflictException e) {
        // 兜底逻辑,理论上因m2重试成功不会走到这里
    }
    m3();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 21:29:52