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}¤tMonth=${month}¤tYear=${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序列化时会无限递归遍历这些关联对象,最终导致栈溢出。
解决方案:
添加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。
- 在
使用DTO(数据传输对象)
创建AppointmentDTO类,只包含前端需要的字段(比如id、fulfilled、appointmentDate、appointmentHour、details、patientLastName、patientFirstName等),后端查询到Appointment后转换为DTO再返回,避免序列化整个实体对象。检查关联实体的反向引用
确认DoctorProfile和PatientProfile类中是否存在@OneToMany(mappedBy = "doctorProfile") private List<Appointment> appointments;这类反向引用,若不需要序列化这些集合,可在对应字段上添加@JsonIgnore注解。
内容的提问来源于stack exchange,提问作者lascone
相关产品推荐
相关产品推荐

