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

Spring+MapStruct+Hibernate更新嵌套实体乐观锁异常求助

问题根因

你遇到的乐观锁异常本质是MapStruct默认集合更新逻辑 + 缺失乐观锁版本字段映射共同导致的,具体拆解如下:

  1. 所有继承BaseEntity的实体都配置了@Version乐观锁字段,但你的DTO定义中没有包含version字段,前端提交的更新请求不会携带现有实体的版本号,MapStruct转换得到的子实体(OnSiteInspection、Attachment)的version字段为默认空值。
  2. MapStruct的updateFromRequest方法默认处理嵌套集合的逻辑是:直接生成新的集合实例替换@MappingTarget实体原有的集合,而非根据ID匹配现有持久态实体做增量更新。当你提交的子实体携带ID时,MapStruct会生成带有ID、但version为空的游离态子实体,替换持久上下文中的原有持久态子实体。
  3. Hibernate执行保存时,发现游离态子实体有ID,会尝试执行更新操作,拿空的version和数据库中已存在的版本号比对,匹配失败就会抛出StaleObjectStateException异常。
  4. 你把ID设为null时能运行的原因是:Hibernate会将无ID的子实体判定为新实例,直接删除原有子实体再插入新数据,跳过了乐观锁校验,但这种做法会导致子实体ID、审计字段全部丢失,产生大量无效删改日志,完全不符合生产环境要求。

解决方案

1. 调整MapStruct集合更新策略,按ID匹配现有实体更新

在你的RealEstateCollateralMapper中自定义嵌套集合的更新逻辑,不要直接替换整个集合,而是根据ID匹配原有集合中的元素做字段更新,保留原有version等系统字段:

// 忽略版本号的子实体单条更新映射
@Mapping(target = "version", ignore = true)
void updateOnSiteInspectionFromDto(OnSiteInspectionDto dto, @MappingTarget OnSiteInspection entity);

@Mapping(target = "version", ignore = true)
void updateAttachmentFromDto(AttachmentDto dto, @MappingTarget Attachment entity);

// 自定义OnSiteInspection集合更新逻辑
default List<OnSiteInspection> updateOnSiteInspectionList(List<OnSiteInspectionDto> dtoList, @MappingTarget List<OnSiteInspection> entityList) {
    if (dtoList == null) {
        entityList.clear();
        return entityList;
    }
    // 现有持久态实体按ID分组
    Map<Integer, OnSiteInspection> existingEntities = entityList.stream()
            .filter(item -> item.getId() != null)
            .collect(Collectors.toMap(BaseEntity::getId, Function.identity()));
    List<OnSiteInspection> newList = new ArrayList<>();
    for (OnSiteInspectionDto dto : dtoList) {
        OnSiteInspection item;
        if (dto.getId() != null && existingEntities.containsKey(dto.getId())) {
            // 匹配到现有实体,仅更新可编辑字段,保留version等系统字段
            item = existingEntities.get(dto.getId());
            updateOnSiteInspectionFromDto(dto, item);
            existingEntities.remove(dto.getId());
        } else {
            // 匹配不到为新增实体,直接转换
            item = requestToOnSiteInspection(dto);
        }
        newList.add(item);
    }
    // 剩余未匹配的实体为需要删除的孤儿实体
    entityList.removeAll(existingEntities.values());
    entityList.clear();
    entityList.addAll(newList);
    return entityList;
}

// 同理添加Attachment集合的自定义更新逻辑
default List<Attachment> updateAttachmentList(List<AttachmentDto> dtoList, @MappingTarget List<Attachment> entityList) {
    // 逻辑和上面一致,匹配ID更新
}

2. 忽略所有更新方法的version字段映射

在父类DtoMapper的updateFromRequest方法,以及所有子实体的更新映射方法上,添加@Mapping(target = "version", ignore = true)注解,禁止从DTO更新乐观锁版本字段,版本号由Hibernate自动维护。

3. (可选)新增并发控制能力

如果需要支持多用户并发修改的冲突校验,可以将version字段添加到DTO定义中,前端查询实体时获取版本号,提交更新时带回,这样多个用户同时修改同一实体时能正确触发乐观锁提示,避免修改被覆盖。


内容的提问来源于stack exchange,提问作者Lord Bug

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 11:15:03