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

Spring Boot+Spring Data Rest双向ManyToMany无限递归问题求助

Fixing Bidirectional ManyToMany Infinite Recursion in Spring Boot & Spring Data Rest

Hey there! Let's tackle that infinite recursion issue you're hitting with your Exam and Subject bidirectional ManyToMany relationship. This is super common when working with JPA bidirectional associations and Spring Data Rest (which uses Jackson under the hood for serialization), so let's break down the solutions.

What's Causing the Issue?

When Jackson tries to serialize an Exam object, it iterates over its subjects collection. Each Subject in that collection has its own exams set pointing back to the original Exam—and this loop keeps going until the stack overflows.

Solution 1: Use @JsonIgnore (Simple One-Way Fix)

If you don't need the reverse association (i.e., you don't care about seeing exams when fetching a Subject), just add @JsonIgnore to one side of the relationship. This tells Jackson to skip serializing that field entirely.

Example for Subject:

public class Subject {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "subject_id")
    Integer subjectId;
    
    @Column(name = "subject_name")
    String subjectName;

    // Ignore this field during serialization to break the loop
    @JsonIgnore
    @ManyToMany
    @JoinTable(
        name = "exam_subject",
        joinColumns = @JoinColumn(name = "subject_id"),
        inverseJoinColumns = @JoinColumn(name = "exam_id")
    )
    Set<Exam> exams = new HashSet<>(0);
}

Pros: Quick and simple.
Cons: The ignored association won't show up in your API responses.

Solution 2: Use @JsonManagedReference & @JsonBackReference (Controlled Bidirectional Fix)

These Jackson annotations explicitly define a parent-child relationship for serialization:

  • @JsonManagedReference: The "forward" side (will be fully serialized)
  • @JsonBackReference: The "back" side (will be skipped, but maintains the relationship for persistence)

Apply them to your entities like this:

import com.fasterxml.jackson.annotation.JsonManagedReference;

public class Exam {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="exam_id")
    Integer examId;
    
    @Column(name="exam_name")
    String examName;

    @JsonManagedReference
    @ManyToMany(mappedBy = "exams")
    Set<Subject> subjects = new HashSet<>(0);
}
import com.fasterxml.jackson.annotation.JsonBackReference;

public class Subject {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "subject_id")
    Integer subjectId;
    
    @Column(name = "subject_name")
    String subjectName;

    @JsonBackReference
    @ManyToMany
    @JoinTable(
        name = "exam_subject",
        joinColumns = @JoinColumn(name = "subject_id"),
        inverseJoinColumns = @JoinColumn(name = "exam_id")
    )
    Set<Exam> exams = new HashSet<>(0);
}

Pros: Maintains the JPA relationship for persistence while avoiding recursion.
Cons: Still only one side of the association appears in responses.

Solution 3: Use @JsonIdentityInfo (Full Bidirectional Fix)

If you need both sides of the association to appear in responses, use @JsonIdentityInfo. This assigns a unique identifier to each entity instance—when Jackson encounters the same entity again, it uses the ID instead of re-serializing the whole object, breaking the loop.

Add this to both entities:

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "examId")
public class Exam {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="exam_id")
    Integer examId;
    
    @Column(name="exam_name")
    String examName;

    @ManyToMany(mappedBy = "exams")
    Set<Subject> subjects = new HashSet<>(0);
}
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "subjectId")
public class Subject {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "subject_id")
    Integer subjectId;
    
    @Column(name = "subject_name")
    String subjectName;

    @ManyToMany
    @JoinTable(
        name = "exam_subject",
        joinColumns = @JoinColumn(name = "subject_id"),
        inverseJoinColumns = @JoinColumn(name = "exam_id")
    )
    Set<Exam> exams = new HashSet<>(0);
}

Pros: Both associations are visible in responses, no recursion.
Cons: Responses will show IDs instead of full objects when entities repeat—your frontend will need to handle this reference look-up.

Quick Note for Spring Data Rest

All these solutions work seamlessly with Spring Data Rest because it relies on Jackson for JSON serialization. As long as you're using the standard Spring Boot starters (like spring-boot-starter-data-rest), Jackson is already included in your project, so no extra dependencies are needed.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:26:15