Spring Boot单元测试中@Version乐观锁失效问题排查
问题
基于Java-Spring Boot-Hibernate-Liquibase构建的应用,生产环境中@Version乐观锁校验正常:传入错误版本参数执行更新会触发预期错误;但在JUnit单元测试中,无论版本参数是否错误,更新操作均能成功执行。是否需要在Liquibase的建表语句中添加配置?
相关代码及测试信息如下:
实体类版本字段:
@Version private int version;
单元测试用例:
@Test void shouldNotUpdateDoctor_wrongVersion() throws Exception { UpdateDoctorCommand updateDoctorCommand = new UpdateDoctorCommand("test4update",LocalDate.of(1980, 5, 12), LocalDate.of(2020, 11, 10), 16000, true, 2,3); String requestJson = objectMapper.writeValueAsString(updateDoctorCommand); postman.perform(MockMvcRequestBuilders.put("/api/v1/clinic/doctors/33") .contentType(MediaType.APPLICATION_JSON) .content(requestJson)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$").value("ROW_UPDATED_IN_ANOTHER_TRANSACTION")); postman.perform(MockMvcRequestBuilders.get("/api/v1/clinic/doctors/33")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("notUpdated")) .andExpect(jsonPath("$.version").value("5")); }
测试报错:
java.lang.AssertionError: Status expected:<400> but was:<200> Expected :400 Actual :200
分析与解决步骤
1. 优先检查Liquibase建表语句的版本字段配置
@Version乐观锁生效的前提是数据库表存在对应版本字段,且Hibernate能正确识别:
- 必须确保表中存在
version字段(与实体类@Version标注的字段名一致),字段类型要和实体类匹配(实体用int则数据库对应INT类型) - 字段不可为NULL,建议设置默认值
0,示例Liquibase配置:<changeSet id="create_doctor_table" author="your-name"> <createTable tableName="doctor"> <!-- 其他字段配置 --> <column name="version" type="INT" defaultValueNumeric="0"> <constraints nullable="false"/> </column> </createTable> </changeSet> - 如果已建表但缺失该字段,需新增Liquibase changeSet添加字段:
<changeSet id="add_version_column_to_doctor" author="your-name"> <addColumn tableName="doctor"> <column name="version" type="INT" defaultValueNumeric="0"> <constraints nullable="false"/> </column> </addColumn> </changeSet>
2. 验证测试数据的初始化逻辑
测试用例预期Doctor的version为5,但传入的更新命令version是3,需确保测试前的数据初始化正确:
- 检查测试数据(如通过
@Sql脚本、测试前手动插入)是否给目标Doctor(ID=33)的version字段赋值为5 - 如果初始化时未设置
version,Hibernate会默认使用0,此时传入3会被识别为合法版本,导致乐观锁不触发
3. 确认业务层的更新逻辑是否正确处理版本参数
乐观锁的触发依赖Hibernate在更新时对比版本号,需确保业务逻辑正确传递版本参数:
- 检查服务层代码是否将
UpdateDoctorCommand中的version赋值给Doctor实体的version字段 - 示例正确逻辑:
public Doctor updateDoctor(Long id, UpdateDoctorCommand command) { Doctor doctor = doctorRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException()); // 必须设置传入的版本号,让Hibernate在更新时做校验 doctor.setVersion(command.getVersion()); doctor.setName(command.getName()); // 其他字段更新 return doctorRepository.save(doctor); } - 如果使用自定义
@Query更新语句,必须手动加入版本号条件(如WHERE id = ?1 AND version = ?2),否则Hibernate不会触发乐观锁校验
4. 排查单元测试的事务隔离问题
如果测试使用了@Transactional,需确保请求间的事务隔离:
- MockMvc的
put和get请求如果在同一个事务中,Hibernate一级缓存会直接读取未提交的更新数据,导致乐观锁校验失效 - 可以在测试方法中去掉
@Transactional,或者在每个请求后手动清空缓存(如调用entityManager.clear())
内容的提问来源于stack exchange,提问作者Pewu
相关产品推荐
相关产品推荐

