I have a Spring application with 2 Entities that have a Many-to-Many relationship. There are Students and Groups. A student can be part of many groups and a group can have many students.
Student Model
@Entity
@Table(name = "STUDENTS")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Student extends AbstractUser {
//Fields
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
@JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { @JoinColumn(name = "student_id") },
inverseJoinColumns = { @JoinColumn(name = "group_id") })
private List<Group> groups = new ArrayList<Group>();
//Constructors
public Student(String password, String firstName, String lastName, String email){
super(password, firstName, lastName, email);
}
public Student(){
}
//Setters and getters
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
}
Group Model
@Entity
@Table(name = "GROUPS")
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Group implements Item, Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, unique = true)
private String name;
@Column(name = "year", nullable = false, length = 1)
private int year;
@ManyToMany(mappedBy = "groups", targetEntity = Student.class)
private List<Student> students;
public Group(String name, int yearOfStudy) {
this.setName(name);
this.setYear(yearOfStudy);
}
...
}
The problem is when I make a request to show all students, if 2 students are in the same group they appear in a hierarchy rather than one after another. What I mean the JSON is going too deep. I don't know how to explain exactly but I'll put an example.
How it appears
[
{
"id": 2,
"password": "$2a$10$bxieGA7kWuEYUUMbYNiYo.SbGpo7X5oh8ulUsqCcrIR2cFN2HiQP2",
"firstName": "First",
"lastName": "Last",
"email": "[email protected]",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0,
"users": [
2,
{
"id": 1,
"password": "$2a$10$9pfTdci7PeHtzxuAxjcOsOjPSswrU35/JOOeWPpgVhJI4tD2YpZdG",
"firstName": "John",
"lastName": "Smith",
"email": "[email protected]",
"groups": [
1
]
}
]
}
]
},
1
]
How it should appear
{
"id": 2,
"password": "$2a$10$bxieGA7kWuEYUUMbYNiYo.SbGpo7X5oh8ulUsqCcrIR2cFN2HiQP2",
"firstName": "First",
"lastName": "Last",
"email": "[email protected]",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0,
}
]
},
{
"id": 1,
"password": "$2a$10$9pfTdci7PeHtzxuAxjcOsOjPSswrU35/JOOeWPpgVhJI4tD2YpZdG",
"firstName": "John",
"lastName": "Smith",
"email": "[email protected]",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0
}]
}
]
Do you have any idea how to solve this? I don't know exactly how to describe my problem and that's why I didn't find a solution yet. Any help will much appreciated. Thank you.
Using the @JsonIgnoreProperties
annotation is another alternative:
@Entity
public class Student extends AbstractUser {
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
@JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { @JoinColumn(name = "student_id") },
inverseJoinColumns = { @JoinColumn(name = "group_id") })
@JsonIgnoreProperties("students")
private List<Group> groups = new ArrayList<Group>();
}
@Entity
public class Group implements Item, Serializable {
@ManyToMany(mappedBy = "groups", targetEntity = Student.class)
@JsonIgnoreProperties("groups")
private List<Student> students;
}
Find comparison between @JsonManagedReference
+@JsonBackReference
, @JsonIdentityInfo
and @JsonIgnoreProperties
here: http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
I've solved it. I've made a custom serializer. So in Group I'll serialize the students by setting a custom annotation @JsonSerialize(using = CustomStudentSerializer.class)
CustomStudentSerializer
public class CustomStudentSerializer extends StdSerializer<List<Student>> {
public CustomStudentSerializer() {
this(null);
}
public CustomStudentSerializer(Class<List<Student>> t) {
super(t);
}
@Override
public void serialize(
List<Student> students,
JsonGenerator generator,
SerializerProvider provider)
throws IOException, JsonProcessingException {
List<Student> studs = new ArrayList<>();
for (Student s : students) {
s.setGroups(null);
studs.add(s);
}
generator.writeObject(studs);
}
}
Did the same for the groups. I've just removed the students/group component when the relationship is already nested. And now it works just fine.
Took me a while to figure this out but I posted here because it may help someone else too.
To solve jackson infinite recursion you can use @JsonManagedReference
, @JsonBackReference
.
@JsonManagedReference is the forward part of reference – the one that gets serialized normally.
@JsonBackReference is the back part of reference – it will be omitted from serialization.
Find more details here: http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
public class Student extends AbstractUser {
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
@JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { @JoinColumn(name = "student_id") },
inverseJoinColumns = { @JoinColumn(name = "group_id") })
@JsonManagedReference
private List<Group> groups = new ArrayList<Group>();
}
public class Group implements Item, Serializable {
@ManyToMany(mappedBy = "groups", targetEntity = Student.class)
@JsonBackReference
private List<Student> students;
}
Or you can use DTO (Data Transfer Object) classes. These are plain code classes wich you can set to your needs when sendind data. For example, for your case you can have:
UserDTO.java:
import java.util.List;
public class UserDTO {
private int id;
private String password;
private String firstName;
private String lastName;
private String email;
private List<GroupDTO> groups;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<GroupDTO> getGroups() {
return groups;
}
public void setGroups(List<GroupDTO> groups) {
this.groups = groups;
}
}
GroupDTO.java
package temp;
public class GroupDTO {
private int id;
private String name;
private int year;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
This way you can customize the information you will send in the json file.
Let's say you have a User class and you want to send all users information:
List<StudentDTO> response = new ArrayList<StudentDTO>(); //List to be send
List<Student> students = bussinessDelegate.findAllStudents(); //Or whatever you do to get all students
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
StudentDTO studentDTO = new StudentDTO(); //Empty constructor
studentDTO.setEmail(student.getEmail());
studentDTO.setFirstName(student.getFirstName());
studentDTO.setLastName(student.getLastName());
studentDTO.setId(student.getId());
studentDTO.setPassword(student.getPassword());
List<Group> studentGroups = student.getGroups();
List<GroupDTO> studentGroupsDTO = new ArrayList<GroupDTO>();
for (int j = 0; j < studentGroups.size(); j++) {
Group group = studentGroups.get(j);
GroupDTO groupDTO = new GroupDTO();
groupDTO.setId(group.getId());
groupDTO.setName(group.getName());
groupDTO.setYear(group.getYear());
studentGroupsDTO.add(groupDTO);
}
studentDTO.setGroups(studentGroupsDTO);
response.add(studentDTO);
}
//Here you have your response list of students ready to send`
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With