Jackson序列化@ManyToMany仅数据库调用触发无限递归问题排查
Hibernate实体转POJO时仅数据库访问触发无限递归问题
问题背景
我用Hibernate EntityBeans实现数据库访问,通过Jackson ObjectMapper将实体对象转换为UI层使用的POJO,为此编写了分层测试:
- 0层测试:简单对象转换(正常)
- 1层测试:带关联结构的对象转换(正常)
- 2层测试:逻辑与1层一致,但涉及持久化操作(触发无限递归错误)
- 3层测试:暂不相关
我不想在@ManyToMany关联上使用@JsonBackreference和@JsonManagedReference,因为需要保留双向数据;而@ManyToOne和@OneToMany关联无此问题。希望先明确问题根源,而非直接编写自定义转换器。
相关类结构
Product实体类
@Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Company.class) public class Product extends ProductBase { // 其他无关代码 @Getter @Setter @ManyToOne @JoinColumn(name = "fk_manufacturer") private Company manufacturer; @Getter @Setter @ManyToMany(fetch = FetchType.LAZY, mappedBy = "supplierProducts") private Set<Company> supplier = new HashSet<>(); // 其他无关代码 }
ProductPOJO类
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Company.class) public class ProductPojo extends ProductBase { // 其他无关代码 @Getter @Setter private CompanyPojo manufacturer; @Getter @Setter private Set<CompanyPojo> supplier = new HashSet<>(); // 其他代码 }
Company实体类
@Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Product.class) public class Company extends CompanyBase { // 其他字段 @Getter @Setter @Column @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "fk_product_manufacturer") private Set<Product> manufacturerProducts = new HashSet<>(); @Getter @Setter @Column @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "supplier_products", joinColumns = {@JoinColumn(name = "fk_company_id")}, inverseJoinColumns = {@JoinColumn(name = "fk_product_id")}) private Set<Product> supplierProducts = new HashSet<>(); // 其他字段 }
CompanyPOJO类
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Company.class) public class CompanyPojo extends CompanyBase { // 代码 @Getter @Setter private Set<ProductPojo> manufacturerProducts = new HashSet<>(); @Getter @Setter private Set<ProductPojo> supplierProducts = new HashSet<>(); // 其他代码... }
错误信息
java.lang.IllegalArgumentException: Infinite recursion (StackOverflowError) (through reference chain: de.bloise.data.persistence.unities.Company["supplierProducts"]->org.hibernate.collection.internal.PersistentSet[0]->de.bloise.data.persistence.product.Product["supplier"]->org.hibernate.collection.internal.PersistentSet[0]->de.bloise.data.persistence.unities.Company["supplierProducts"]->.....
问题根源与解决思路
核心差异分析
两种测试场景的本质区别在于集合类型:
- 无数据库访问时,使用的是普通
HashSet,Jackson能通过@JsonIdentityInfo正确识别对象身份,第一次序列化后会记录对象ID,后续遇到同一对象时仅输出ID,避免递归。 - 数据库访问后,Hibernate会把集合替换为
PersistentSet(懒加载代理集合)。这个代理类在Jackson序列化时,会干扰@JsonIdentityInfo的身份识别逻辑——Jackson无法正确追踪代理集合内对象的已序列化状态,导致循环引用无法被终止,最终触发栈溢出。
另外,你的@JsonIdentityInfo配置存在错误:
- Product实体的
scope = Company.class配置错误,scope应该指向当前类(Product.class),用于区分不同类型对象的ID范围;同理Company实体的scope = Product.class也应改为Company.class。POJO类的scope也需对应修正(比如ProductPojo的scope应为ProductPojo.class),否则Jackson无法正确区分实体和POJO的对象身份,加剧递归问题。
解决步骤
修正@JsonIdentityInfo的scope配置
每个类的scope都应指向自身:- Product实体:
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Product.class) - Company实体:
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Company.class) - ProductPojo:
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ProductPojo.class) - CompanyPojo:
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = CompanyPojo.class)
- Product实体:
处理Hibernate持久化集合
有两种方式:- 手动转换:在实体转POJO时,将
PersistentSet复制为普通HashSet,比如:productPojo.setSupplier(new HashSet<>(product.getSupplier())); - 使用Jackson Hibernate模块:引入
jackson-datatype-hibernate5依赖,它能自动处理Hibernate的懒加载代理和持久化集合,正确识别对象身份,无需手动转换。
- 手动转换:在实体转POJO时,将
验证双向关联序列化
修正后,Jackson会在首次序列化对象时输出完整数据,后续遇到同一对象时仅输出ID,既保留双向关联数据,又避免无限递归。
内容的提问来源于stack exchange,提问作者Tony B
相关产品推荐
相关产品推荐

