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

复合主键下@OneToOne映射中@JsonIgnore失效致栈溢出问题

问题原因与解决方案

原因分析

  1. Lombok @Data的循环调用
    @Data注解会自动生成toString()、equals()、hashCode()方法,这些方法会递归遍历关联对象。你的代码中,Parent包含Child引用,Child的复合主键CompKey又包含Parent引用,调用这些自动生成的方法时会触发无限递归,直接导致栈溢出——@JsonIgnore只作用于Jackson序列化,无法阻止Lombok生成方法的循环调用。

  2. Jackson对嵌入式主键中注解的识别限制
    即使解决了Lombok的问题,@JsonIgnore在嵌入式主键类CompKey中可能无法被Jackson正确识别。当序列化Parent时,Jackson会递归处理Child的compKey字段,此时嵌入类中的@JsonIgnore无法打断双向引用的序列化循环,依然会引发栈溢出。

  3. OneToOne默认立即加载的递归加载
    JPA中@OneToOne关联默认是立即加载(FetchType.EAGER),加载Parent时会立即加载关联的Child,加载Child时又会加载其compKey中的Parent,形成无限递归的加载逻辑,进一步加剧栈溢出问题。

解决方案

方案1:修复Lombok的循环调用问题

给双向关联的字段添加Lombok的排除注解,阻止自动生成的方法递归调用关联对象:

  • 在Parent类的child字段添加@ToString.Exclude和@EqualsAndHashCode.Exclude:
@Entity
@AllArgsConstructor
@Table(name = "parent")
@Data
public class Parent {
    @Id
    @Column
    private String id;

    @OneToOne(mappedBy = "compKey.parent")
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private Child child;
}
  • 同时在CompKey类的parent字段添加同样的排除注解:
@Embeddable
@Data
@AllArgsConstructor
public static class CompKey implements Serializable {
    @OneToOne
    @JoinColumn
    @JsonIgnore
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private Parent parent;

    @Column
    private String name;
}

方案2:调整JPA关联的加载策略

将@OneToOne的加载方式改为懒加载,避免立即触发递归加载:

// Parent类中
@OneToOne(mappedBy = "compKey.parent", fetch = FetchType.LAZY)
@ToString.Exclude
@EqualsAndHashCode.Exclude
private Child child;

// CompKey类中
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn
@JsonIgnore
@ToString.Exclude
@EqualsAndHashCode.Exclude
private Parent parent;

注意:OneToOne懒加载需要确保Hibernate的代理配置正常,可在实体类添加@Proxy(lazy = true),或在配置文件中启用相关设置。

方案3:规范复合主键的映射方式(推荐)

嵌入式主键应使用基本类型的外键字段,而非直接关联实体,再通过@MapsId关联实体与主键字段,从根源避免循环引用:

  1. 修改CompKey为基本类型字段:
@Embeddable
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class CompKey implements Serializable {
    // 用字符串类型存储外键,而非实体引用
    private String parentId;
    private String name;
}
  1. 修改Child类,单独维护与Parent的关联并映射主键:
@Entity
@Table(name = "child")
@Data
@NoArgsConstructor
public class Child {
    @EmbeddedId
    private CompKey compKey;

    @Column
    private String test;

    // 通过@MapsId关联主键中的parentId字段
    @OneToOne
    @MapsId("parentId")
    @JoinColumn(name = "parent_id")
    @JsonIgnore
    @ToString.Exclude
    private Parent parent;
}
  1. 修改Parent类的关联配置:
@Entity
@AllArgsConstructor
@Table(name = "parent")
@Data
public class Parent {
    @Id
    @Column
    private String id;

    @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY)
    @ToString.Exclude
    private Child child;
}

这种方式既保留了Child表复合主键包含parent_id的结构,又避免了主键中直接关联实体导致的循环问题,注解也能正常生效。

内容的提问来源于stack exchange,提问作者Something Nice

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 04:39:52