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

为何标记FetchType.LAZY仍出现JSON无限递归栈溢出?

问题

定义了Country和State两个JPA实体类,二者是一对多关联关系。State实体中关联的Country字段标记了fetch = FetchType.LAZY,但序列化JSON时仍会触发关联数据加载,导致Country与State互相引用的无限递归,最终抛出StackOverflowError。已知可以通过在Country类的states字段添加@JsonIgnore解决,但疑惑为何标记LAZY后仍会加载State数据?


Country.java

package com.bezkoder.springjwt.models;

import javax.persistence.*;
import java.util.List;

@Entity
public class Country {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "country", cascade = CascadeType.ALL)
    //@JsonIgnore
    private List<State> states;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<State> getStates() {
        return states;
    }

    public void setStates(List<State> states) {
        this.states = states;
    }

    @Override
    public String toString() {
        return "Country{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

State.java

package com.bezkoder.springjwt.models;

import javax.persistence.*;
import java.util.List;

@Entity
public class State {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "country_id")
    private Country country;

    @OneToMany(mappedBy = "state", cascade = CascadeType.ALL)
    private List<City> cities;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Country getCountry() {
        return country;
    }

    public void setCountry(Country country) {
        this.country = country;
    }

    public List<City> getCities() {
        return cities;
    }

    public void setCities(List<City> cities) {
        this.cities = cities;
    }
    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        return id != null && id.equals(((State) obj).id);
    }

    @Override
    public int hashCode() {
        return 2021;
    }
    @Override
    public String toString() {
        return "State{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

异常信息

Hibernate: select country0_.id as id1_1_, country0_.name as name2_1_ from country country0_
Hibernate: select states0_.country_id as country_3_10_0_, states0_.id as id1_10_0_, states0_.id as id1_10_1_, states0_.country_id as country_3_10_1_, states0_.name as name2_10_1_ from state states0_ where states0_.country_id=?
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 873
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 873
2024-03-16 17:54:22.052  WARN 31312 --- [nio-8771-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->com.bezkoder.springjwt.models.State["country"]->com.bezkoder.springjwt.models.Country["states"]->org.hibernate.collection.internal.PersistentBag[0]->

解答

为什么LAZY加载仍会触发State数据加载?

核心是你混淆了JPA懒加载的生效逻辑和JSON序列化的触发时机:

  1. JPA懒加载的作用对象搞反了:State的country字段标记FetchType.LAZY,是指查询State时不会立即加载关联的Country,只有调用getCountry()才触发加载。但你的场景是先查询了Country,Country的states集合默认就是LAZY加载(JPA规范中@OneToMany默认FetchType为LAZY),Hibernate返回的是一个代理集合(比如PersistentBag)。当JSON序列化框架(Jackson)要序列化Country对象时,会调用getStates()获取集合,然后遍历集合元素,这就触发了Hibernate加载State数据的SQL查询。
  2. 循环引用的连锁反应:加载State后,Jackson会继续序列化每个State对象,调用getCountry()获取关联的Country。此时如果Spring的Open Session in View模式开启(默认开启),Hibernate会话还没关闭,会立即执行SQL重新加载Country,形成Country -> State -> Country -> State...的无限循环,最终导致栈溢出。

解决方案

除了你提到的@JsonIgnore,还有几种更灵活的处理方式:

  • 使用Jackson双向引用注解:在Country的states字段加@JsonManagedReference,在State的country字段加@JsonBackReference。Jackson会自动识别双向关联,只序列化正向的Country到State的引用,忽略反向的State到Country引用。
  • 使用DTO隔离实体与序列化:创建专门的CountryDTO和StateDTO,只包含需要返回给前端的字段,在服务层将实体对象转换为DTO后再返回。从根源上避免循环引用和不必要的字段暴露。
  • 关闭Open Session in View:如果不需要在视图层访问懒加载数据,可以关闭该模式。这样序列化时调用懒加载属性会直接抛出异常,强制你提前在业务层处理数据加载逻辑,但可能影响其他依赖懒加载的业务场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:35:54