如何配置MapStruct映射一对多双向关联并遵守非空字段约束?
MapStruct一对多实体映射:解决多端非空约束问题
问题背景
我有一对处于一对多关联关系的实体,希望在将DTO映射为实体时遵守多端的非空引用约束,但不确定如何通过MapStruct实现。
现有DTO定义
data class PointInTimeWriteDTO( val title: String, val note: String, val symptoms: Set<SymptomDTO> )
data class SymptomDTO ( val category: String, val symptom: String, val strength: Strength )
对应实体类
@Entity @Table(name = "point_in_time") class PointInTime( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long, val title: String, val note: String, @OneToMany(mappedBy = "symptom") val symptoms: Set<Symptom> ) { @field:Column(updatable = false) val createdAt: Instant = Instant.now() }
@Entity @Table(name = "symptom") @IdClass(SymptomId::class) class Symptom( @ManyToOne(optional = false) @Id @JoinColumn(name = "point_in_time_id", nullable = false) var pointInTime: PointInTime, @Id val category: String, @Id val symptom: String, val strength: Strength )
当前Mapper配置
@Mapper(componentModel = "spring", unmappedSourcePolicy = ReportingPolicy.WARN) abstract class PointInTimeMapper { abstract fun toBean(pointInTimeWriteDTO: PointInTimeWriteDTO): PointInTime abstract fun toSymptomBean(symptomDTO: SymptomDTO): Symptom }
问题点
Mapper尝试将Symptom.pointInTime设为null,触发空指针异常。需要先映射不含symptoms的PointInTime,再映射关联该PointInTime的symptoms并添加到主实体中。
解决方案
通过自定义映射方法+@AfterMapping注解即可简洁实现需求,核心思路是先完成主实体的基础映射,再在后续步骤中绑定子实体的关联关系:
步骤1:修改Mapper接口
@Mapper(componentModel = "spring", unmappedSourcePolicy = ReportingPolicy.WARN) abstract class PointInTimeMapper { // 1. 先映射PointInTime的基础属性,忽略symptoms字段 @Mapping(target = "symptoms", ignore = true) abstract fun toBean(pointInTimeWriteDTO: PointInTimeWriteDTO): PointInTime // 2. 定义带主实体参数的Symptom映射方法,MapStruct会自动绑定关联关系 abstract fun toSymptomBean(symptomDTO: SymptomDTO, pointInTime: PointInTime): Symptom // 3. 在主实体映射完成后,执行子实体的映射与关联 @AfterMapping fun bindSymptoms(@MappingTarget pointInTime: PointInTime, dto: PointInTimeWriteDTO) { val symptomEntities = dto.symptoms.map { toSymptomBean(it, pointInTime) }.toSet() // 注意:若PointInTime的symptoms是不可变Set,需改为MutableSet或提供添加方法 (pointInTime.symptoms as MutableSet).addAll(symptomEntities) } }
关键细节说明
@Mapping(target = "symptoms", ignore = true):禁止MapStruct自动处理symptoms字段,避免提前生成无关联的Symptom实例。- 带
PointInTime参数的toSymptomBean:MapStruct会自动生成实现逻辑,将传入的主实体赋值给Symptom的pointInTime属性,满足非空约束。 @AfterMapping方法:在主实体映射完成后触发,负责将DTO中的SymptomDTO转换为关联好主实体的Symptom,并添加到主实体的集合中。
实体类调整(可选)
如果PointInTime的symptoms是不可变Set,建议改为MutableSet以方便后续添加元素:
@Entity @Table(name = "point_in_time") class PointInTime( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long, val title: String, val note: String, @OneToMany(mappedBy = "symptom") val symptoms: MutableSet<Symptom> = mutableSetOf() ) { @field:Column(updatable = false) val createdAt: Instant = Instant.now() }
内容的提问来源于stack exchange,提问作者Shade
相关产品推荐
相关产品推荐

