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
相关产品推荐
相关产品推荐

