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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 00:37:08