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

SpringBoot前后端交互获取数据时出现StackOverflowError求助

StackOverflowError问题求助:Jackson序列化实体时栈溢出

报错信息

java.lang.StackOverflowError: null
    at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017) ~[na:na]
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[na:na]
    at com.fasterxml.jackson.databind.JsonMappingException.prependPath(JsonMappingException.java:455) ~[jackson-databind-2.15.3.jar:2.15.3]
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:790) ~[jackson-databind-2.15.3.jar:2.15.3]
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[jackson-databind-2.15.3.jar:2.15.3]

前端JS代码

function getAppointments(button) {
        const day = button.getAttribute('data-day');
        const month = button.getAttribute('data-month');
        const year = button.getAttribute('data-year');

        fetch(`/getAppointments?day=${day}&currentMonth=${month}&currentYear=${year}`)
            .then(response => response.json())
            .then(data => {

                // 在'appointments' div中显示结果
                const appointmentsDiv = document.getElementById('appointments');
                appointmentsDiv.innerHTML = ''; // 清空之前的内容

                // 为每个预约创建div
                data.forEach(appointment => {

                    const appointmentDiv = document.createElement('div');
                    appointmentDiv.classList.add('appointment');

                    // 添加加粗的姓名
                    const nameDiv = document.createElement('div');
                    nameDiv.innerHTML = `<strong>${appointment.patientProfile.lastName}, ${appointment.patientProfile.firstName}</strong>`;
                    appointmentDiv.appendChild(nameDiv);

                    // 添加日期和时间
                    const dateTimeDiv = document.createElement('div');
                    dateTimeDiv.textContent = `Date & Time: ${appointment.appointmentDate}, ${appointment.appointmentHour}`;
                    appointmentDiv.appendChild(dateTimeDiv);

                    // 添加详情
                    const detailsDiv = document.createElement('div');
                    detailsDiv.textContent = `Details: ${appointment.details}`;
                    appointmentDiv.appendChild(detailsDiv);

                    // 根据fulfilled值添加对应按钮
                    const fulfilledDiv = document.createElement('div');
                    fulfilledDiv.classList.add('fulfilled');
                    if (appointment.fulfilled) {
                        fulfilledDiv.textContent = "Fulfilled";
                    } else {
                        const button = document.createElement('button');
                        button.textContent = "Fulfill";
                        button.addEventListener('click', () => markAsFulfilled(appointment.id));
                        fulfilledDiv.appendChild(button);

                        const prescriptionButton = document.createElement('button');
                        prescriptionButton.textContent = "Create Prescription";
                        prescriptionButton.addEventListener('click', () => redirectToPrescriptionCreation(appointment.id));
                        appointmentDiv.appendChild(prescriptionButton);
                    }
                    appointmentDiv.appendChild(fulfilledDiv);

                    // 将预约div添加到主div中
                    appointmentsDiv.appendChild(appointmentDiv);
                });
            })
            .catch(error => console.log('Error fetching appointments:', error));
    }

后端代码

@GetMapping("/getAppointments")
@ResponseBody
public List<Appointment> getAppointments(@RequestParam int day, @RequestParam int currentMonth, @RequestParam int currentYear){
    return calendarService.getAppointments(day, currentMonth, currentYear);
}

@Override
public List<Appointment> getAppointments(int day, int currentMonth, int currentYear) {
    DoctorProfile doctorProfile = doctorProfileRepository.findByUserId(userService.getAuthenticationUser().getId());
    LocalDate localDate = LocalDate.of(currentYear, currentMonth, day);
    return appointmentRepository.findAllByDoctorProfileIdAndAppointmentDate(doctorProfile.getId(), localDate);
}

Appointment实体类代码

@Table
@Entity
@Data
public class Appointment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(columnDefinition = "boolean default false")
    private boolean fulfilled;

    @Column(name = "appointment_date")
    private LocalDate appointmentDate;

    @Column(name = "appointment_hour")
    private LocalTime appointmentHour;

    private String details;

    @ManyToOne
    @JoinColumn(name ="doctor_id")
    private DoctorProfile doctorProfile;

    @ManyToOne
    @JoinColumn(name="patient_id")
    private PatientProfile patientProfile;

    @OneToOne(mappedBy = "appointment", cascade = CascadeType.ALL)
    private Prescription prescription;

    public Appointment(DoctorProfile doctorProfile, PatientProfile patientProfile){
        this.doctorProfile = doctorProfile;
        this.patientProfile = patientProfile;
    }

    public Appointment(){}

    // Appointment类的hashCode重写
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        result = prime * result + (fulfilled ? 1231 : 1237);
        result = prime * result + ((appointmentDate == null) ? 0 : appointmentDate.hashCode());
        result = prime * result + ((appointmentHour == null) ? 0 : appointmentHour.hashCode());
        result = prime * result + ((details == null) ? 0 : details.hashCode());
        result = prime * result + ((doctorProfile == null || doctorProfile.getId() == 0) ? 0 : doctorProfile.getId());
        result = prime * result + ((patientProfile == null || patientProfile.getId() == 0) ? 0 : patientProfile.getId());
        return result;
    }

    @Override
    public String toString() {
        return "Appointment{" +
                "id=" + id +
                ", fulfilled=" + fulfilled +
                ", appointmentDate=" + appointmentDate +
                ", appointmentHour=" + appointmentHour +
                ", details='" + details + '\'' +
                '}';
    }
}

问题分析与解决方法

从报错栈信息可以看出,问题出在Jackson序列化实体对象的环节,根源是实体之间的双向循环引用:Appointment关联了DoctorProfile和PatientProfile,而DoctorProfile或PatientProfile中大概率存在反向引用的List<Appointment>集合,Jackson序列化时会无限递归遍历这些关联对象,最终导致栈溢出。

解决方案:

  1. 添加Jackson注解打破循环引用

    • 在Appointment类的doctorProfile和patientProfile字段上添加@JsonIgnore注解,直接忽略这些关联对象的序列化:
      @ManyToOne
      @JoinColumn(name ="doctor_id")
      @JsonIgnore
      private DoctorProfile doctorProfile;
      
      @ManyToOne
      @JoinColumn(name="patient_id")
      @JsonIgnore
      private PatientProfile patientProfile;
      
    • 或者使用@JsonManagedReference和@JsonBackReference区分双向关联的主动端和被动端:在Appointment的关联字段上标注@JsonBackReference,在DoctorProfile/PatientProfile的List<Appointment>字段上标注@JsonManagedReference。
  2. 使用DTO(数据传输对象)
    创建AppointmentDTO类,只包含前端需要的字段(比如id、fulfilled、appointmentDate、appointmentHour、details、patientLastName、patientFirstName等),后端查询到Appointment后转换为DTO再返回,避免序列化整个实体对象。

  3. 检查关联实体的反向引用
    确认DoctorProfile和PatientProfile类中是否存在@OneToMany(mappedBy = "doctorProfile") private List<Appointment> appointments;这类反向引用,若不需要序列化这些集合,可在对应字段上添加@JsonIgnore注解。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 10:57:33