Spring Boot JPA+Flyway实现一对多关系遇无限递归问题求助
解决Spring Boot中JPA双向关联导致的JSON无限递归问题
你使用Spring Boot 3.0.6实现JPA一对多关联(membership_plan对应多个membership_plan_features),实体类和Flyway建表语句均已编写,但调用membershipPlanRepository.findAll()查询时,返回的JSON出现无限递归,同时抛出HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError)异常。
问题根源
问题出在JPA双向关联的循环引用:MembershipPlan包含planFeature列表,每个MembershipPlanFeature又引用回MembershipPlan,Jackson序列化JSON时会不断在两者之间循环嵌套,最终触发栈溢出。
解决方案
1. 用@JsonIgnore忽略反向关联
在MembershipPlanFeature的membershipPlan字段上添加@JsonIgnore,让Jackson序列化时跳过该字段,直接切断循环链:
@Entity @Table(name = "membership_plan_feature") @Getter @Setter public class MembershipPlanFeature { // 其他字段保持不变 @ManyToOne @JoinColumn(name="plan_id") @JsonIgnore // 添加此注解 private MembershipPlan membershipPlan; }
2. 使用@JsonManagedReference + @JsonBackReference
这是Jackson专门处理双向关联的注解组合:
- 在一对多的
MembershipPlan中,给planFeature字段加@JsonManagedReference(主动引用,会被正常序列化) - 在多对一的
MembershipPlanFeature中,给membershipPlan字段加@JsonBackReference(反向引用,序列化时会被忽略)
修改后的实体类:
@Entity @Table(name = "membership_plan") public class MembershipPlan { // 其他字段保持不变 @OneToMany (mappedBy = "membershipPlan") @JsonManagedReference // 添加此注解 private List<MembershipPlanFeature> planFeature; }
@Entity @Table(name = "membership_plan_feature") @Getter @Setter public class MembershipPlanFeature { // 其他字段保持不变 @ManyToOne @JoinColumn(name="plan_id") @JsonBackReference // 添加此注解 private MembershipPlan membershipPlan; }
3. 使用@JsonIdentityInfo生成唯一标识
给实体类添加唯一标识注解,Jackson再次遇到相同实体时,会用标识代替重复的对象序列化:
@Entity @Table(name = "membership_plan") @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "planId") public class MembershipPlan { // 字段内容不变 }
@Entity @Table(name = "membership_plan_feature") @Getter @Setter @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class MembershipPlanFeature { // 字段内容不变 }
序列化后的JSON会变成:
[ { "planId": 1, "orgId": "101", "planName": "GOLD", "planPrice": "29", "planFeature": [ { "id": 1, "orgId": "101", "featureName": "Kanban", "featureDesc": "Kanban", "membershipPlan": 1 // 用planId代替完整对象 } ] } ]
4. 使用DTO(数据传输对象)解耦
创建仅包含前端所需字段的DTO类,避免直接序列化JPA实体:
// MembershipPlanDTO.java public class MembershipPlanDTO { private Long planId; private String orgId; private String planName; private String planPrice; private List<MembershipPlanFeatureDTO> planFeature; // 构造方法、getter/setter } // MembershipPlanFeatureDTO.java public class MembershipPlanFeatureDTO { private Long id; private String orgId; private String featureName; private String featureDesc; // 构造方法、getter/setter }
查询后将实体转换为DTO返回(可手动转换或用MapStruct等工具):
List<MembershipPlan> plans = membershipPlanRepository.findAll(); List<MembershipPlanDTO> planDTOs = plans.stream().map(plan -> { MembershipPlanDTO dto = new MembershipPlanDTO(); dto.setPlanId(plan.getPlanId()); dto.setOrgId(plan.getOrgId()); dto.setPlanName(plan.getPlanName()); dto.setPlanPrice(plan.getPlanPrice()); dto.setPlanFeature(plan.getPlanFeature().stream().map(feature -> { MembershipPlanFeatureDTO featureDTO = new MembershipPlanFeatureDTO(); featureDTO.setId(feature.getId()); featureDTO.setOrgId(feature.getOrgId()); featureDTO.setFeatureName(feature.getFeatureName()); featureDTO.setFeatureDesc(feature.getFeatureDesc()); return featureDTO; }).collect(Collectors.toList())); return dto; }).collect(Collectors.toList()); return planDTOs;
内容的提问来源于stack exchange,提问作者Abhi
相关产品推荐
相关产品推荐

