如何通过单元测试验证OptimisticLockException的触发行为?
解决乐观锁测试不触发OptimisticLockException的问题
核心问题分析
你当前的测试用例里,entity_1和entity_2的ID分别是789和987,这是两个完全独立的数据库记录。乐观锁冲突只会发生在同一个ID的实体实例、版本号不一致的场景下,所以第二次更新自然不会抛出异常。
具体修正步骤
1. 调整测试用例,使用同一ID的两个实体实例
创建两个ID相同、初始版本号一致的EntityA实例,模拟两个线程/事务同时加载同一条记录的场景:
// 创建两个ID相同、版本号都是2的实体实例 EntityA entity_1 = createEntity("789", 2); EntityA entity_2 = createEntity("789", 2); // 先更新第一个实例,此时数据库中该记录的版本号会被更新(比如变成3) service.update(entity_1, "attr1"); // 再更新第二个实例,它的版本号还是旧的2,此时触发乐观锁冲突 assertThrows(OptimisticLockException.class, () -> service.update(entity_2, "attr2")); verify(jpaRepository, times(2)).save(any(EntityA.class));
2. 修改update方法,确保异常被抛出
你的update方法里捕获了OptimisticLockException但只打日志没重新抛出,导致测试代码无法捕获到异常。需要调整方法逻辑:
public void update(EntityA entityA, String attribute) { try { entityA.setSomeAttribute(attribute); jpaRepository.save(entityA); } catch (OptimisticLockException e) { log.debug(e.getMessage()); // 重新抛出异常,让上层(测试代码)能捕获到 throw e; } }
3. 单元测试中模拟JPA的版本检查逻辑(如果使用Mock)
如果你的测试是用Mock框架(比如Mockito)模拟JpaRepository,需要手动模拟版本冲突时抛出异常的场景:
// 第一次save正常执行,模拟版本号更新 when(jpaRepository.save(argThat(entity -> entity.getId().equals("789") && entity.getLastChange().equals(new Timestamp(2))))) .thenAnswer(invocation -> { EntityA entity = invocation.getArgument(0); entity.setLastChange(new Timestamp(3)); return entity; }); // 第二次save时,因为版本号不匹配,抛出OptimisticLockException when(jpaRepository.save(argThat(entity -> entity.getId().equals("789") && entity.getLastChange().equals(new Timestamp(2))))) .thenThrow(new OptimisticLockException("版本冲突"));
4. 注意版本字段Timestamp的使用细节
使用Timestamp作为版本字段时,要确保JPA能正确识别版本变更:
- 避免手动设置
lastChange字段,让JPA自动维护(如果需要手动更新,确保每次更新时版本字段的值严格递增) - 也可以考虑改用
Long类型的版本号,更直观且不易出现时间精度问题:
@Version private Long version;
内容的提问来源于stack exchange,提问作者emoleumassi
相关产品推荐
相关产品推荐

