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

Spring JPA事务中并发请求重复记录问题的解决方案咨询

Spring JPA 并发场景下实现“存在则复用,不存在则创建”的正确姿势

针对你遇到的并发问题,核心原因是先查询再插入的操作不是原子性的,并发请求会同时通过“不存在”的检查,导致唯一约束冲突。以下是几种可靠的解决方案,按推荐优先级排序:

1. 捕获唯一约束异常,回退查询(通用方案,高并发友好)

利用数据库唯一约束的原子性,直接尝试插入记录——成功则返回新记录,失败(唯一约束冲突)则查询已存在的记录复用。这种方式不依赖特定数据库,是最通用的并发安全方案。

代码示例:

@Transactional
public YourEntity getOrCreate(Long id) {
    try {
        YourEntity newEntity = new YourEntity();
        newEntity.setId(id);
        // 填充其他业务属性
        return yourEntityRepository.save(newEntity);
    } catch (PersistenceException e) {
        // 校验是否为唯一约束冲突异常(不同数据库异常类型/错误码不同)
        if (isUniqueKeyViolation(e)) {
            // 异常后查询已存在的记录
            return yourEntityRepository.findById(id)
                    .orElseThrow(() -> new RuntimeException("无法获取目标记录"));
        }
        // 非唯一约束异常,正常抛出
        throw e;
    }
}

// 辅助方法:判断是否为唯一约束冲突(以MySQL为例)
private boolean isUniqueKeyViolation(PersistenceException e) {
    Throwable rootCause = e.getCause();
    return rootCause instanceof SQLIntegrityConstraintViolationException 
            && ((SQLIntegrityConstraintViolationException) rootCause).getErrorCode() == 1062;
}

说明

  • 数据库的唯一约束是最可靠的并发屏障,确保同一ID只能插入一次
  • 事务保证插入失败时不会留下脏数据,回退后查询的是已提交的有效记录
  • 注意:不同数据库的唯一约束异常类型和错误码不同,需要根据你使用的数据库调整判断逻辑(比如PostgreSQL的错误码是23505)

2. MySQL专属:使用INSERT ... ON DUPLICATE KEY UPDATE

如果你的数据库是MySQL,可以直接用原生SQL实现原子性的“存在则忽略,不存在则插入”,避免异常捕获的开销。

代码示例:

// Repository层定义原生SQL方法
@Repository
public interface YourEntityRepository extends JpaRepository<YourEntity, Long> {
    @Modifying
    @Transactional
    @Query(value = "INSERT INTO your_entity (id, name, status) VALUES (:id, :name, :status) " +
            "ON DUPLICATE KEY UPDATE id = id", // 冲突时不做任何更新,仅触发约束检查
            nativeQuery = true)
    void insertOrIgnore(@Param("id") Long id, @Param("name") String name, @Param("status") Integer status);
}

// 服务层调用
@Transactional
public YourEntity getOrCreate(Long id, String name, Integer status) {
    yourEntityRepository.insertOrIgnore(id, name, status);
    // 插入或忽略后,直接查询即可拿到目标记录
    return yourEntityRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("记录获取失败"));
}

说明

  • ON DUPLICATE KEY UPDATE id = id 是一个空操作,仅利用MySQL的唯一约束逻辑判断记录是否存在
  • 整个操作是数据库层面的原子操作,完全避免并发问题,性能比捕获异常更好

3. 悲观锁方案(适合低并发场景)

通过JPA的悲观锁机制,在查询时锁住目标记录,确保同一时间只有一个请求能执行“查询-插入”逻辑。

代码示例:

@Transactional
public YourEntity getOrCreate(Long id) {
    // 加悲观写锁,其他请求必须等待当前事务释放锁才能查询
    YourEntity existing = yourEntityRepository.findById(id, LockModeType.PESSIMISTIC_WRITE).orElse(null);
    if (existing != null) {
        return existing;
    }
    // 不存在则创建
    YourEntity newEntity = new YourEntity();
    newEntity.setId(id);
    // 填充属性
    return yourEntityRepository.save(newEntity);
}

说明

  • 悲观锁会降低并发性能,因为其他请求需要等待锁释放,适合并发量不高的场景
  • 注意锁的粒度,避免锁住过多数据影响整体性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:12:35