You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 16:15:40