JPA双向ManyToOne/OneToMany关联触发StackOverflow异常求助
问题根源
栈溢出和JPA的加载策略没有关系,核心是两个双向关联的实体都使用了Lombok的@Data注解,@Data会自动生成包含所有字段的toString()、equals()、hashCode()方法,触发循环调用:
- 打印/序列化Employee时,会调用其company字段的toString()
- Company的toString()又会调用employees集合中每个Employee的toString(),形成无限递归
如果接口是返回JSON格式数据,JSON序列化框架(比如Jackson)在序列化双向关联对象时也会触发同样的循环递归问题。
解决方案
根据你双向关联查询均可正常运行的业务需求,可以选择以下任意一种方案:
方案1:修改Lombok注解,排除关联字段
不使用全量的@Data,手动排除关联字段在通用方法中的生成:
- 给CompanyEntity的employees字段加
@ToString.Exclude、@EqualsAndHashCode.Exclude注解 - 给EmployeeEntity的company字段加
@ToString.Exclude、@EqualsAndHashCode.Exclude注解
示例调整代码:
// EmployeeEntity 调整 @Entity @Table(name = "employee") @Data @Builder @AllArgsConstructor @NoArgsConstructor public class EmployeeEntity { @Id @Column(name = "id") private Long id; @Column(name = "name") private String name; @ManyToOne @JoinColumn(name = "company_id", nullable = false) @ToString.Exclude @EqualsAndHashCode.Exclude private CompanyEntity company; } // CompanyEntity 调整 @Entity @Table(name = "company") @Data @Builder @AllArgsConstructor @NoArgsConstructor public class CompanyEntity { @Id @Column(name = "id") private Long id; @Column(name = "name") private String name; @OneToMany(mappedBy = "company") @ToString.Exclude @EqualsAndHashCode.Exclude private Set<EmployeeEntity> employees = new HashSet<>(); }
方案2:添加JSON序列化忽略注解(针对接口返回JSON场景)
如果是接口序列化时触发的栈溢出,可以在关联字段上添加Jackson的忽略注解:
- 可选择在任意一端的关联字段加
@JsonIgnore直接截断循环 - 也可以使用
@JsonManagedReference和@JsonBackReference配对实现双向序列化:- 在EmployeeEntity的company字段上加
@JsonBackReference - 在CompanyEntity的employees字段上加
@JsonManagedReference
这样序列化时会自动截断循环,员工信息不会序列化所属公司的员工列表,公司信息序列化时会包含下属员工列表。
- 在EmployeeEntity的company字段上加
方案3:使用DTO层做数据转换
最稳妥的业务实现方案是不直接返回实体类给前端,额外定义员工DTO、公司DTO,查询到实体后手动拷贝需要的字段到DTO中,完全避免实体关联带来的序列化问题,也符合分层开发的规范。
内容的提问来源于stack exchange,提问作者VeryNiceArgumentException
相关产品推荐
相关产品推荐

