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

POST更新用户家属信息时createdAt字段被置空的问题排查

问题分析

更新已有家属信息时createdAt被置为null的核心原因:

  1. 前端更新操作不会传递createdAt字段,导致DTO中的createdAt为null
  2. MapStruct默认会将DTO的null值映射到实体对象,覆盖了从数据库查询出的原有createdAt值
  3. @PreUpdate仅负责更新updatedAt,不会处理createdAt的null覆盖问题
解决方案

方案一:修改MapStruct映射规则(推荐)

通过配置MapStruct忽略null值的属性映射,避免DTO中的null覆盖实体原有字段:

@Named("UserMapper")
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {
    UserMapper mapper = Mappers.getMapper(UserMapper.class);

    UserDTO toDto(UserEntity entity);

    // 全局忽略null值映射
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    UserEntity toEntity(UserDTO dto);

    List<UserDTO> toDto(List<UserEntity> entity);

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    List<UserEntity> toEntity(List<UserDTO> dto);
}

或者针对单个字段精准控制:

@Mapping(target = "createdAt", ignore = true)
UserEntity toEntity(UserDTO dto);

方案二:实体层添加字段保护

修改UserDependentsEntity的setCreatedAt方法,仅允许在字段为空时赋值:

public void setCreatedAt(LocalDateTime createdAt) {
    // 仅当当前createdAt为null时才赋值,更新操作保留原有创建时间
    if (this.createdAt == null) {
        this.createdAt = createdAt;
    }
}

方案三:服务层合并原有数据

在更新时手动合并原有家属的createdAt字段,避免直接覆盖列表:

public void updatePreferencesAndDependents(UUID userId, UpdatePreferencesDependentsDTO updateDTO) {
    var user = getUserById(userId);
    // 更新用户偏好字段
    user.setReferralHospital(updateDTO.getReferralHospital());
    user.setReferralHospitalId(updateDTO.getReferralHospitalId());
    user.setAttendance(updateDTO.getAttendance());
    user.setCoverage(updateDTO.getCoverage());
    user.setAccommodation(updateDTO.getAccommodation());
    user.setPartaking(updateDTO.getPartaking());

    // 合并家属数据,保留原有createdAt
    List<UserDependentsDTO> existingDependents = user.getUserDependents();
    List<UserDependentsDTO> updatedDependents = updateDTO.getUserDependents();

    if (updatedDependents != null) {
        Map<UUID, UserDependentsDTO> existingMap = existingDependents.stream()
                .collect(Collectors.toMap(UserDependentsDTO::getUuid, d -> d));

        List<UserDependentsDTO> mergedDependents = updatedDependents.stream()
                .map(dto -> {
                    UserDependentsDTO existing = existingMap.get(dto.getUuid());
                    if (existing != null) {
                        dto.setCreatedAt(existing.getCreatedAt());
                    }
                    return dto;
                })
                .collect(Collectors.toList());

        user.setUserDependents(mergedDependents);
    }

    var entity = UserMapper.mapper.toEntity(user);
    repository.save(entity);
}
方案对比
  • 方案一:最简洁高效,利用MapStruct原生特性解决,无需额外业务逻辑,优先推荐
  • 方案二:在实体层做兜底防护,避免任何场景下的意外覆盖,适合作为补充措施
  • 方案三:灵活性最高,适合需要精细控制字段更新的复杂业务场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 15:14:58