Spring Data JPA一对一关联出现StackOverflowError的原因排查
这个StackOverflowError的核心原因是双向一对一关联的实体在序列化过程中触发了无限递归,具体来说有两种常见场景:
1. JSON序列化的无限递归循环
你在Address的hotel字段上加了@JsonIgnore,但如果你的业务逻辑(比如Spring MVC接口返回保存后的Hotel对象)需要序列化Hotel时,Jackson(默认的JSON序列化库)会遍历Hotel的addr属性,而当序列化Address对象时,若框架内部的序列化逻辑触发了反向引用,就会陷入Hotel → Address → Hotel → Address...的无限循环,直到栈内存被耗尽。
2. 自动生成的toString方法导致递归
很多IDE(比如IDEA、Eclipse)自动生成的toString方法会包含关联对象的完整引用:
Hotel的toString输出addr的全部信息Address的toString又输出hotel的全部信息
当框架日志打印实体对象,或者你手动调用toString时,就会触发Hotel.toString() → Address.toString() → Hotel.toString()...的无限递归,直接导致栈溢出。
解决方法
方案一:用Jackson注解明确序列化方向
推荐使用@JsonManagedReference和@JsonBackReference标记双向关联的父子关系,彻底解决JSON序列化递归问题:
// Hotel类 public class Hotel { String name; @OneToOne(mappedBy = "hotel", cascade = CascadeType.ALL) @JsonManagedReference // 标记为被序列化的"父"端 Address addr; // getter、setter、toString(建议去掉addr的完整引用,改为只输出addr.street或id) } // Address类 public class Address { String street; @OneToOne @JoinColumn(name = "hotel") @JsonBackReference // 标记为不被序列化的"子"端,避免递归 Hotel hotel; // getter、setter、toString(建议去掉hotel的完整引用,改为只输出hotel.name或id) }
方案二:修改toString方法
如果是toString导致的递归,直接修改实体类的toString方法,移除关联对象的完整引用,只保留简单属性:
比如Hotel的toString可以改成:
@Override public String toString() { return "Hotel{name='" + name + "', addrStreet='" + (addr != null ? addr.getStreet() : "null") + "'}"; }
Address的toString改成:
@Override public String toString() { return "Address{street='" + street + "', hotelName='" + (hotel != null ? hotel.getName() : "null") + "'}"; }
小优化:简化保存代码
虽然不是错误原因,但双向关联的保存代码可以简化(因为cascade = CascadeType.ALL会级联保存关联对象):
Address addr = new Address("3333 Elvin"); Hotel hotel = new Hotel("someHotel"); addr.setHotel(hotel); hotel.setAddr(addr); // 这行保证内存中对象关系完整,可选但推荐 hotelDao.save(hotel);
内容的提问来源于stack exchange,提问作者JasminDan

