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

Hibernate并发插入已存在记录时需触发失败而非执行更新

解决Hibernate save并发插入时自动更新同主键记录的问题

问题场景

我有一个Web/Rest服务,接收携带业务ID的新记录,通过Hibernate EntityManager执行插入操作。但多进程并发执行插入时,Hibernate的save方法会在同主键记录已存在时自动执行更新而非插入,导致记录被覆盖。

例如两个进程同时执行以下代码:
process1:

MySimple r = new MySimple(1, "tagP1");
mySimpleRepository.save(r);

process2:

MySimple r = new MySimple(1, "tagP2");
mySimpleRepository.save(r);

我需要实现一种机制,让记录已存在时的更新操作直接失败,但目前save方法即使处理分离实体也不会触发失败,而是直接覆盖更新。

对应的实体表SQL:

create table MySimple(
 SIMPLE_ID INTEGER NOT NULL,
 TAG VARCHAR(200),
 primary key PK_MYSIMPLE (SIMPLE_ID)
)

实体类与Repository定义:

@Entity
public class MySimple {
 @Id
 @Column(name="SIMPLE_ID", nullable=false, updatable=false)
 private Integer simpleId;

 @Column(name="TAG", nullable=false, updatable=true)
 private String tag;
 // getters/setters 省略
}

public interface MySimpleRepository extends JpaRepository<MySimple, Integer> {
}

可行解决方案

方案1:数据库唯一约束+捕获SQL异常

利用主键本身的唯一约束,当重复插入时数据库会抛出唯一键冲突异常,我们捕获该异常并转为业务异常即可。这种方式依赖数据库的原子性检查,能彻底避免并发竞态问题。

修改Service层代码:

@Service
@Transactional
public class MySimpleService {
    @Autowired
    private MySimpleRepository repository;

    public void insertOnly(MySimple entity) {
        try {
            repository.saveAndFlush(entity);
        } catch (DataIntegrityViolationException e) {
            if (e.getCause() instanceof SQLIntegrityConstraintViolationException) {
                throw new RuntimeException("记录已存在,无法执行插入");
            }
            // 其他数据完整性异常原样抛出
            throw e;
        }
    }
}

方案2:前置检查+事务隔离(需配合高隔离级别)

在执行save前先查询记录是否存在,若存在则直接抛出异常。但要注意,高并发下检查和插入之间存在时间窗口,可能出现竞态条件,需要配合事务隔离级别(如SERIALIZABLE)来增强可靠性。

@Service
@Transactional(isolation = Isolation.SERIALIZABLE)
public class MySimpleService {
    @Autowired
    private MySimpleRepository repository;

    public void insertOnly(MySimple entity) {
        if (repository.existsById(entity.getSimpleId())) {
            throw new RuntimeException("记录已存在,禁止插入");
        }
        repository.saveAndFlush(entity);
    }
}

注意:SERIALIZABLE隔离级别会降低数据库并发性能,需根据业务场景权衡使用。

方案3:乐观锁(仅适用于需防止并发更新的场景)

如果你的需求是防止并发更新而非完全禁止更新,可以给实体添加版本字段实现乐观锁。但此方案无法直接阻止已存在记录的更新,仅能在并发更新时触发异常,不符合当前"记录存在则更新失败"的核心需求,仅作补充参考。

修改实体类添加版本字段:

@Entity
public class MySimple {
 @Id
 @Column(name="SIMPLE_ID", nullable=false, updatable=false)
 private Integer simpleId;

 @Column(name="TAG", nullable=false, updatable=true)
 private String tag;

 @Version
 @Column(name="VERSION")
 private Integer version;
 // getters/setters 省略
}

当并发更新同一记录时,Hibernate会自动检查版本号,版本不匹配时抛出OptimisticLockingFailureException。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 14:45:46