Spring Boot Hibernate中带@Version字段的DTO转Entity更新问题
解决@Version字段映射时乐观锁失效的问题
你遇到的核心问题是:从数据库加载的Entity处于JPA持久化上下文(Persistence Context)中,JPA会管控它的状态,导致MapStruct无法修改@Version字段,进而跳过了乐观锁的版本校验。下面给你几个实用的解决办法:
方法1:手动前置版本校验(推荐)
在映射更新字段之前,直接对比DTO传入的版本和数据库实体的版本,不一致就立刻抛异常,从源头阻止错误更新:
Entity fromDB = getEntity(eDto.getId()); // 先做版本校验,不通过直接抛乐观锁异常 if (!Objects.equals(eDto.getVersion(), fromDB.getVersion())) { throw new OptimisticLockingFailureException("实体已被其他用户修改,请刷新后重试"); } // 校验通过后,再映射更新其他业务字段 mapper.toEntity(fromDB, eDto); // 执行保存(此时JPA会自动更新版本号) saveEntity(fromDB);
这个方式逻辑直白,不需要修改映射器配置,还能自定义异常提示,对业务代码侵入性低。
方法2:通过MapStruct的@AfterMapping自动校验
如果多个地方都需要做版本校验,可以把校验逻辑放到MapStruct映射器里,用@AfterMapping注解在映射完成后触发校验:
@Mapper public interface EntityMapper { EntityMapper INSTANCE = Mappers.getMapper(EntityMapper.class); // 映射业务字段(MapStruct会自动忽略@Version的修改,因为持久化上下文管控) void toEntity(@MappingTarget Entity e, EntityDto eDto); // 映射后自动执行版本校验 @AfterMapping default void validateVersion(@MappingTarget Entity e, EntityDto eDto) { if (!Objects.equals(eDto.getVersion(), e.getVersion())) { throw new OptimisticLockingFailureException("版本不一致,更新操作被阻止"); } } }
之后业务代码里只需要调用映射方法即可,校验逻辑统一维护在映射器中。
方法3:脱离持久化上下文后修改版本
如果一定要通过修改实体版本号来触发JPA的乐观锁校验,可以先把实体从持久化上下文 detach,变成游离状态后再修改版本,最后合并回上下文:
Entity fromDB = getEntity(eDto.getId()); // 将实体从持久化上下文移除,变成游离状态 entityManager.detach(fromDB); // 此时可以修改版本号 fromDB.setVersion(eDto.getVersion()); // 映射其他业务字段 mapper.toEntity(fromDB, eDto); // 合并回持久化上下文,JPA会对比版本号并触发乐观锁校验 entityManager.merge(fromDB);
这个方式需要依赖EntityManager,逻辑相对复杂,一般只在特殊场景下使用。
内容的提问来源于stack exchange,提问作者Ewe
相关产品推荐
相关产品推荐

