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

Spring Boot+JPA项目Swagger测试API遇JSON无限递归问题求助

Spring Boot JPA JSON无限递归(StackOverflowError)排查与解决

核心原因

实体类之间的双向关联导致Jackson序列化时出现循环引用,反复序列化关联对象最终触发栈溢出。

排查与解决方法

1. 检查@JsonManagedReference/@JsonBackReference的使用正确性

这两个注解必须成对出现在双向关联的两端,且位置不能搞反:

  • @JsonManagedReference:加在一对多关系中"一"的一方的集合属性上,标识该端是主动序列化的一方
  • @JsonBackReference:加在一对多关系中"多"的一方的关联对象属性上,标识该端会被忽略序列化,避免循环

示例代码:

// 一的一方(Patient)
@Entity
public class Patient {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @JsonManagedReference
    @OneToMany(mappedBy = "patient", cascade = CascadeType.ALL)
    private List<Appointment> appointments;

    // getter、setter、构造方法
}

// 多的一方(Appointment)
@Entity
public class Appointment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private LocalDateTime appointmentTime;

    @JsonBackReference
    @ManyToOne
    @JoinColumn(name = "patient_id")
    private Patient patient;

    // getter、setter、构造方法
}

2. 使用@JsonIdentityInfo打破循环引用

如果存在多层双向关联,或者上述注解无效,可以给关联实体添加@JsonIdentityInfo,通过唯一ID标识实体,序列化时重复引用会直接用ID代替,避免递归:

示例代码:

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Appointment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private LocalDateTime appointmentTime;

    @ManyToOne
    @JoinColumn(name = "patient_id")
    private Patient patient;

    // 其他字段与方法
}

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Patient {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "patient")
    private List<Appointment> appointments;

    // 其他字段与方法
}

3. 改用DTO(数据传输对象)返回结果

直接返回实体类容易暴露不必要的关联数据,也会引发序列化问题,建议创建DTO层,只封装前端需要的字段:

// 定义AppointmentDTO
public class AppointmentDTO {
    private Long id;
    private LocalDateTime appointmentTime;
    private String patientName;

    // 构造方法、getter、setter
}

// 业务层转换实体为DTO
@Service
public class AppointmentService {
    @Autowired
    private AppointmentRepository appointmentRepository;

    public AppointmentDTO getAppointmentById(Long id) {
        Appointment appointment = appointmentRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Appointment not found"));
        
        AppointmentDTO dto = new AppointmentDTO();
        dto.setId(appointment.getId());
        dto.setAppointmentTime(appointment.getAppointmentTime());
        dto.setPatientName(appointment.getPatient().getName());
        
        return dto;
    }
}

4. 全局配置Jackson序列化深度

如果不想修改实体类,可以在配置文件中限制Jackson的序列化深度,避免无限递归:

在application.yml中添加:

spring:
  jackson:
    serialization:
      max-depth: 2 # 根据实际关联层级调整数值
      fail-on-empty-beans: false

额外检查点

  • 确认所有双向关联的实体对都做了正确的序列化注解处理(比如Appointment与Doctor的关联也可能存在循环)
  • 检查Swagger是否启用了全局序列化扩展,是否覆盖了实体类的注解配置

内容的提问来源于stack exchange,提问作者Nguyen Le Hoang Chinh Chinh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 22:35:54