MapStruct一对多映射失败:循环依赖与字段未映射问题求助
解决MapStruct循环引用+字段不映射问题
问题现象
- 实体添加一对多关联后,Spring应用启动失败,提示
StudentMapper与ProgramMapper存在循环引用 - 引入
CycleAvoidingMappingContext后仍无法启动,怀疑Lombok的@Builder注解导致,尝试禁用builder配置无效 - 移除所有类的
@Builder后应用可启动,但MapStruct生成的映射类仅通过空构造方法创建对象,所有字段值为null,未执行任何属性赋值
相关依赖
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.6.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.6.3</version> </dependency>
相关代码
Program实体类
@AllArgsConstructor @NoArgsConstructor @Data @Builder @Entity public class Program { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany(mappedBy = "enrolledProgram", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Student> enrolledStudents; }
ProgramDTO类
@AllArgsConstructor @NoArgsConstructor @Data @Builder public class ProgramDTO { private Long id; @NotNull private String name; @NotNull private Set<StudentDTO> enrolledStudents; }
Student实体类
@NoArgsConstructor @AllArgsConstructor @Data @Builder @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToOne @JoinColumn(name = "program_id") private Program enrolledProgram; }
StudentDTO类
@AllArgsConstructor @NoArgsConstructor @Data @Builder public class StudentDTO { private Long id; @NotNull private String name; @NotNull private ProgramDTO enrolledProgram; }
CycleAvoidingMappingContext类
public class CycleAvoidingMappingContext { private Map<Object, Object> knownInstances = new IdentityHashMap<>(); @BeforeMapping public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) { return (T) knownInstances.get(source); } @BeforeMapping public void storeMappedInstance(Object source, @MappingTarget Object target) { knownInstances.put(source, target); } }
ProgramMapper接口
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.CONSTRUCTOR, uses = {StudentMapper.class} ) public interface ProgramMapper { Program toProgram(ProgramDTO programDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration ProgramDTO toProgramDTO(Program program, @Context CycleAvoidingMappingContext context); }
StudentMapper接口
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.CONSTRUCTOR, uses = {ProgramMapper.class} ) public interface StudentMapper { Student toStudent(StudentDTO studentDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration StudentDTO toStudentDTO(Student student, @Context CycleAvoidingMappingContext context); }
生成的ProgramMapperImpl示例
@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2025-05-27T08:09:19-0600", comments = "version: 1.6.3, compiler: javac, environment: Java 24 (Oracle Corporation)" ) @Component public class ProgramMapperImpl implements ProgramMapper { @Override public Program toProgram(ProgramDTO programDTO, CycleAvoidingMappingContext context) { Program target = context.getMappedInstance( programDTO, Program.class ); if ( target != null ) { return target; } if ( programDTO == null ) { return null; } Program program = new Program(); context.storeMappedInstance( programDTO, program ); return program; } @Override public ProgramDTO toProgramDTO(Program program, CycleAvoidingMappingContext context) { ProgramDTO target = context.getMappedInstance( program, ProgramDTO.class ); if ( target != null ) { return target; } if ( program == null ) { return null; } ProgramDTO programDTO = new ProgramDTO(); context.storeMappedInstance( program, programDTO ); return programDTO; } }
解决方案
1. 解决循环引用问题
循环引用的核心原因是两个Mapper通过uses互相依赖,且使用了构造器注入策略,Spring无法处理循环依赖的构造器注入。
修改方案:
将两个Mapper的injectionStrategy改为InjectionStrategy.FIELD,让Spring通过字段注入解决循环依赖,同时保留CycleAvoidingMappingContext处理对象映射时的循环引用(防止栈溢出)。
修改后的ProgramMapper:
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD, uses = {StudentMapper.class} ) public interface ProgramMapper { Program toProgram(ProgramDTO programDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration ProgramDTO toProgramDTO(Program program, @Context CycleAvoidingMappingContext context); }
修改后的StudentMapper:
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD, uses = {ProgramMapper.class} ) public interface StudentMapper { Student toStudent(StudentDTO studentDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration StudentDTO toStudentDTO(Student student, @Context CycleAvoidingMappingContext context); }
2. 解决字段不映射问题
去掉@Builder后字段不映射,是因为MapStruct处理带@Context参数的方法时,需要显式声明映射关系,或者确保字段名称完全匹配。同时关联集合的映射需要明确指定使用对应Mapper的方法。
修改Mapper接口,显式声明字段映射:
修改后的ProgramMapper:
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD, uses = {StudentMapper.class} ) public interface ProgramMapper { @Mapping(target = "enrolledStudents", source = "enrolledStudents") Program toProgram(ProgramDTO programDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration @Mapping(target = "enrolledStudents", source = "enrolledStudents") ProgramDTO toProgramDTO(Program program, @Context CycleAvoidingMappingContext context); }
修改后的StudentMapper:
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD, uses = {ProgramMapper.class} ) public interface StudentMapper { @Mapping(target = "enrolledProgram", source = "enrolledProgram") Student toStudent(StudentDTO studentDTO, @Context CycleAvoidingMappingContext context); @InheritInverseConfiguration StudentDTO toStudentDTO(Student student, @Context CycleAvoidingMappingContext context); }
如果要保留@Builder,无需额外配置,MapStruct会自动识别构建器,只要确保上述循环引用的解决方案已生效即可。
3. 验证步骤
- 清理项目编译目录(如
target),重新编译,确保MapStruct生成最新的实现类 - 启动Spring应用,检查是否再出现循环引用异常
- 编写测试代码,验证实体与DTO之间的映射是否正确,字段值是否正常赋值
内容的提问来源于stack exchange,提问作者Michael Millar
相关产品推荐
相关产品推荐

