Spring JPA保存双向关联实体时触发java.lang.StackOverflowError问题
问题分析与解决方案
问题根源
你遇到的栈溢出是因为Lombok的@Data注解自动生成的equals/hashCode方法导致了循环引用:
- Customer的hashCode会计算orders集合的hashCode,而集合里的Order的hashCode又会计算关联的Customer的hashCode
- 这种双向调用会无限循环,最终触发StackOverflowError
解决办法
方案1:用@EqualsAndHashCode.Exclude排除关联字段
在双向关联的字段上添加该注解,让Lombok生成equals/hashCode时忽略这些字段,打破循环:
修改Customer.java
@Entity @Table(name="customer") @Data public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) @EqualsAndHashCode.Exclude // 排除orders字段,避免循环计算hashCode private Set<Order> orders = new HashSet<>(); public void add(Order order) { if(order != null) { if(orders == null) { this.orders = new HashSet<>(); } this.orders.add(order); order.setCustomer(this); } } }
或者选择修改Order.java(二选一即可):
@Entity @Table(name="order") @Data public class Order { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String orderTrackingNumber; private String status; @ManyToOne @JoinColumn(name = "customer_id") @EqualsAndHashCode.Exclude // 排除customer字段,打破循环 private Customer customer; }
方案2:手动实现equals和hashCode
放弃@Data的自动生成,只基于实体的主键(id)来实现equals和hashCode,这样既保证实体判断的正确性,又避免关联字段的循环:
比如Customer的手动实现:
@Entity @Table(name="customer") @Getter @Setter @ToString // 注意toString如果包含orders,需避免循环,可添加@ToString.Exclude public class Customer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) @ToString.Exclude // 可选,避免toString时的循环输出 private Set<Order> orders = new HashSet<>(); public void add(Order order) { if(order != null) { if(orders == null) { this.orders = new HashSet<>(); } this.orders.add(order); order.setCustomer(this); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return Objects.equals(id, customer.id); } @Override public int hashCode() { return Objects.hash(id); } }
Order同理,只基于id实现equals和hashCode即可。
补充说明
注释order.setCustomer(this)后错误消失,是因为此时Order的customer字段为null,计算Order的hashCode时不会调用Customer的hashCode,自然不会触发循环,但这样关联关系没有建立,所以数据库order表的customer_id字段无法写入,这不是正确的解决方式。
内容的提问来源于stack exchange,提问作者Junaid S.
相关产品推荐
相关产品推荐

