skip to Main Content

I have two JPA entities with a @ManyToMany relationship, for example Student and Course, something like that:

Student Entity:

@ManyToMany

List<Course> courses;

Course Entity:

@ManyToMany

List<Student> students;

What is the best way/pattern to implement a Spring REST controller which returns students and courses? I would like to see inside the JSON Student the list of the courses enrolled, but stopping the recursion deep to the first level (to avoid a stack overflow). I would like the same for the JSON Course. In other words, I would like to see a student with a list of enrlolled courses inside, but do not want the list of students inside every course again, to avoid recursion. I know about @JsonBackReference an so on, but this is not the case because I want to see the lists in both the JSON’s entities involved.

I know there are already some topics about this problem on stackoverflow, but I didn’t find anything really definitive.

Could anybody give me some hints?

Best regards,

Bruno

4

Answers


  1. You can make projections to prevent the StackOverFlowError errors like:

    public class StudentProjection {
       private String id;
       private String firstname;
       ..
       ..
       List<CourseProejction> courses;
       // getters and setters
    }
    
    public class CourseProjection {
       private String id;
       private String name;
       ..
       ..
       List<StudentProjection> students;
       // getters and setters
    }
    

    And for StudentRepository:

    @Query("select new com.example.demo.projection.StudentProjection(s.id, s.firstname) from Student s left join s.courses c where c.name = ?1)
    List<StudentProjection> findAllByCoursesIn_Name(String name);
    

    With the query above, you will get list of the students with enrolled courses. Please correct me if i misunderstood the problem.

    Login or Signup to reply.
  2. IMO, the best solution for this problem would be to make two different read models for each case, including its own endpoint.

    This means that you would have a read model (and endpoint) for students with a list of their enrolled courses, and a separate read model for courses and their enrolled students.

    There’s no real reason why these two views should be coupled so tightly that they cause the problems that you describe, and so I would simply decouple them.

    So, something like:

    public class EnrolledStudent {
      String id;
      List<Course> courses;
      // ...
    }
    
    public class Course {
      String id;
      String name;
      Instructor instructor;
      // ...
    }
    
    public class CourseEnrollment {
      String id;
      List<Student> students;
      // ...
    }
    
    public class Student {
      String id;
      String firstName;
      // ...
    }
    

    while this does cause a bit of model bloat, it also forces you to think about the design of your API a lot more – note that I’ve called the third class above a CourseEnrollment – which is not necessarily a Course! Similarly, does a response containing Student really need to list all the fields of the courses that the student is enrolled in? Or would a subset of the data as a separate read-view suffice?

    IMO this added model bloat is worth the design tradeoffs.

    Login or Signup to reply.
  3. You could try @JsonIgnoreProperties("field name") (where you would put @JsonManagedReference/@JsonBackReference), it will omit the field name so no recursion in json response…

    But long term a DTO is a nice solution, since it would return that instead of the entity, so changes in entity won’t break api that much.

    Login or Signup to reply.
  4. To handle the recursion issue you can make use of Jackson’s @JsonManagedReference and @JsonBackReference annotations. These annotations allow you to control the serialization and deserialization process to prevent infinite recursion.
    Here is the example below how you can implement:

    Student Entity

    @Entity
    public class Student {
    
        // Other fields and annotations
        
        @JsonManagedReference
        @ManyToMany
        private List<Course> courses;
    
        // Getters and setters
    }
    

    Course Entity

    @Entity
    public class Course {
    
        // Other fields and annotations
        
        @JsonBackReference
        @ManyToMany(mappedBy = "courses")
        private List<Student> students;
    
        // Getters and setters
    }
    

    In the Student entity, we use @JsonManagedReference to mark the courses field. This indicates that the serialization of the courses list will be managed by the Student entity.

    In the Course entity, we use @JsonBackReference to mark the students field. This indicates that the serialization of the students list will be handled by the opposite side of the relationship (i.e., the Student entity). This prevents the recursion issue by breaking the cycle.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search