JPA乐观锁未抛出OptimisticLockingException问题咨询
JPA乐观锁在并发删除场景下未触发异常反而插入数据的问题
问题背景
我给JPA实体添加了乐观锁字段:
@Version var version: Long? = null
模拟并发更新与删除的测试中,预期触发OptimisticLockingException,前置条件是数据库中有2条数据。测试代码如下:
@Transactional fun update(id: String){ val existing = repository.findById(id) // 模拟另一个事务删除ID为2的条目 // some concurrent code execution deleting the entry with id 2 in different transaction // 修改ID为2的条目 repo.saveAll(existing) }
实际调试发现,数据库中ID为2的条目已被并发删除,但调用saveAll时并未抛出预期异常,反而重新插入修改后的条目,覆盖了删除操作。
问题根源
JPA乐观锁依赖版本字段校验,但saveAll的执行逻辑是:
- 若实体是托管状态且数据库中存在对应记录,执行
UPDATE语句,此时会带上版本字段条件,不匹配则抛出OptimisticLockingException。 - 若数据库中已无对应记录,JPA会将该实体判定为新实体,执行
INSERT操作,完全跳过乐观锁校验。
当并发事务删除记录后,当前事务中的实体虽然是之前查询得到的托管对象,但数据库中已无匹配记录,因此触发了插入逻辑而非更新逻辑。
解决方案
1. 保存前显式校验实体存在性
在执行saveAll前,逐个检查实体是否仍存在于数据库,不存在则主动抛出异常:
@Transactional fun update(id: String){ val existingEntities = repository.findById(id) // 模拟并发删除操作 // 校验每个实体是否存在 existingEntities.forEach { entity -> if (!repository.existsById(entity.id)) { throw OptimisticLockingException("Entity with ID ${entity.id} was deleted by another transaction") } } // 修改实体属性 existingEntities.find { it.id == "2" }?.apply { // 执行修改逻辑 } repo.saveAll(existingEntities) }
2. 使用JPQL自定义更新语句
通过自定义带版本和ID条件的更新语句,利用返回的受影响行数判断操作是否成功:
// 在Repository接口中定义方法 @Modifying @Query("UPDATE YourEntity e SET e.yourField = :newValue, e.version = e.version + 1 WHERE e.id = :entityId AND e.version = :currentVersion") fun updateWithLockCheck(@Param("entityId") id: String, @Param("newValue") value: String, @Param("currentVersion") version: Long): Int // 业务方法中调用 @Transactional fun update(id: String){ val existingEntity = repository.findById("2").orElseThrow() // 模拟并发删除操作 // 执行更新并检查受影响行数 val affectedRows = repository.updateWithLockCheck(existingEntity.id, "updatedValue", existingEntity.version!!) if (affectedRows == 0) { throw OptimisticLockingException("Entity was deleted or modified by another transaction") } }
这种方式直接在数据库层面执行带版本校验的更新,若记录已被删除,受影响行数为0,此时可抛出异常。
3. 利用EntityManager进行状态校验
通过EntityManager查询数据库最新状态,对比版本和存在性:
@Autowired private lateinit var entityManager: EntityManager @Transactional fun update(id: String){ val existingEntities = repository.findById(id) // 模拟并发删除操作 existingEntities.forEach { entity -> val dbEntity = entityManager.find(entity::class.java, entity.id) when { dbEntity == null -> throw OptimisticLockingException("Entity deleted concurrently") dbEntity.version != entity.version -> throw OptimisticLockingException("Entity version mismatch") else -> { // 将修改后的属性复制到dbEntity(保持托管状态) dbEntity.yourField = entity.yourField } } } entityManager.flush() }
EntityManager.find会直接查询数据库最新状态,若记录不存在则返回null,可直接抛出异常;若存在则对比版本,不一致也抛出异常。
内容的提问来源于stack exchange,提问作者midnight
相关产品推荐
相关产品推荐

