Hibernate中@OneToOne关联调用Mongo保存时引发StackOverflowException
这个问题我之前在项目里碰到过好几次,本质就是双向一对一关联引发的序列化无限递归。结合你用Mongo保存的场景,原因很明确:你的Invoice和InvoiceSequence是双向关联的,当Mongo通过Jackson序列化实体时,Invoice里包含InvoiceSequence,而InvoiceSequence又反向引用Invoice,这样序列化器会在两个对象之间无限循环,最终导致栈内存耗尽,抛出StackOverflowException。
下面是几个经过验证的解决方案,按推荐程度排序:
1. 使用Jackson专用注解处理双向关联(最推荐)
Jackson提供了@JsonManagedReference和@JsonBackReference这对注解,专门用来解决双向关联的序列化循环问题:
- 在主实体(这里是
Invoice,因为它的关联字段用了mappedBy,表示是关联维护端的反向)的关联字段上标记@JsonManagedReference,这个注解会让序列化器正常处理该字段 - 在从实体(
InvoiceSequence,关联的维护端)的invoice字段上标记@JsonBackReference,这个注解会让序列化器忽略该字段的反向引用,避免循环
修改后的代码示例:
Invoice类
@Entity @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name = "invoices") @JsonIgnoreProperties(ignoreUnknown = true) public class Invoice implements Serializable { private static final long serialVersionUID = 1L; @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") @Column(columnDefinition = "CHAR(36)") @Id private String id; @OneToOne(cascade = CascadeType.ALL, mappedBy = "invoice") @JsonManagedReference // 新增这个注解 private InvoiceSequence invoiceSequence; // ... 其他字段和方法 }
InvoiceSequence类
@Entity @Table(name = "invoice_sequences") public class InvoiceSequence implements Serializable { private static final long serialVersionUID = 1L; // ... 其他字段 @OneToOne @JoinColumn(name = "invoice_id") // 假设你有这个注解 @JsonBackReference // 新增这个注解 private Invoice invoice; // ... 其他字段和方法 }
2. 用@JsonIgnore直接打断循环
如果不需要在序列化Invoice时包含InvoiceSequence数据,可以直接在Invoice的关联字段上添加@JsonIgnore注解,强制序列化器忽略该字段:
@OneToOne(cascade = CascadeType.ALL, mappedBy = "invoice") @JsonIgnore // 新增这个注解 private InvoiceSequence invoiceSequence;
这个方案简单粗暴,但缺点是会完全丢失关联数据的序列化输出,适合不需要返回关联对象的场景。
3. 处理Hibernate懒加载代理的潜在问题
如果你的关联字段用了懒加载(虽然@OneToOne默认是FetchType.EAGER,但如果手动改成LAZY),Hibernate会生成代理对象,序列化时也可能引发异常。可以在实体类上扩展@JsonIgnoreProperties,忽略Hibernate的代理相关属性:
@JsonIgnoreProperties(ignoreUnknown = true, value = {"hibernateLazyInitializer", "handler"}) public class Invoice implements Serializable { // ... }
额外注意点
如果是用Spring Data MongoDB做保存操作,确保你的实体类的序列化是由Jackson处理的(这是默认配置),上面的注解都能生效。另外,保存前最好确认实体对象的关联关系没有形成闭环,比如不要同时让Invoice和InvoiceSequence互相引用对方的实例,不过正常的Hibernate关联不会出现这种情况,主要还是序列化的问题。
内容的提问来源于stack exchange,提问作者Orkun

