Spring Boot双向OneToMany映射新增关联后出现StackOverflowError问题求助
为啥加了Recipe和House的关联后,Account和Member突然报StackOverflowError了?
这个问题的核心是循环引用触发了无限递归,而你用的Lombok @Data注解刚好放大了这个问题,咱们一步步理清楚:
之前Account-Member的双向关联为啥没问题?
你用@JsonBackReference和@JsonManagedReference搞定了JSON序列化的循环问题,所以接口返回正常。但@Data会自动生成equals()、hashCode()和toString()方法,这些方法默认会把所有字段(包括关联的实体对象)都包含进去——之前没出问题是因为递归层级还没到栈溢出的程度,或者刚好没触发这些方法的调用。新增House-Account关联后为啥炸了?
当你加上Recipe→House→Account的关联后,肯定在House实体里加了指向Account的字段(比如private Account account;),而Account里大概率也有对应的House关联字段(一对一关联通常是双向的)。这就形成了两条循环链:Account ↔ Member+Account ↔ House。
一旦某个场景触发了实体的toString()、equals()(比如日志打印、ORM加载实体时的内部操作、调试时查看对象),就会陷入无限递归:- Account的toString调用Member的toString
- Member的toString又调用Account的toString
- 同时Account的toString还会调用House的toString
- House的toString再调用Account的toString
这种多层递归很快就会把栈内存耗光,直接抛出StackOverflowError。
怎么解决这个问题?
给你几个实用的方案,按推荐程度排序:
1. 给Lombok的自动生成方法“减减肥”
@Data虽爽,但在实体关联多的场景下容易踩坑。可以换更精细的注解,或者排除关联字段:
- 方案A:替换@Data为@Getter+@Setter
如果不需要自动生成equals()、hashCode()、toString(),直接用这俩注解,从根源上避免自动生成的方法触发循环。 - 方案B:保留@Data,但排除关联字段
用Lombok的@ToString.Exclude和@EqualsAndHashCode.Exclude标记所有关联字段,比如:
在Account实体里:
在Member实体里:@OneToMany( cascade = {CascadeType.ALL}, orphanRemoval = true, mappedBy = "fkAccountId") @JsonBackReference @ToString.Exclude @EqualsAndHashCode.Exclude private Set<Member> members; // 假设Account里有House关联字段,同样加上这两个注解 @OneToOne(mappedBy = "account") @ToString.Exclude @EqualsAndHashCode.Exclude private House house;@ManyToOne @JoinColumn(name = "fkAccountId", nullable = false) @JsonManagedReference @ToString.Exclude @EqualsAndHashCode.Exclude private Account fkAccountId;
2. 给新增的House-Account双向关联加JSON注解
你之前处理了Account-Member的JSON循环,但House和Account的一对一关联也得处理,防止序列化时的递归问题:
- 比如在House的Account字段上加
@JsonBackReference,在Account的House字段上加@JsonManagedReference(或者反过来,看你需要哪个方向的序列化)。
3. 手动重写toString()、equals()、hashCode()
如果需要自定义这些方法,可以自己写,只包含实体自身的基础属性(比如id、email、name这些),不要包含关联的实体对象,彻底杜绝递归调用的可能。
总结
说白了就是双向关联导致的循环引用,被Lombok自动生成的方法给触发了无限递归。调整下Lombok的注解,或者手动控制方法逻辑,就能解决这个问题啦。
内容的提问来源于stack exchange,提问作者Z-100

