JPA many-to-many relationship causing infinite recursion and stack overflow error
Asked Answered
H

5

10

I'm working on an EclipseLink project in which one user can "follow" another as can be done on social media sites. I have this set up with a User entity (referencing a table called users) which has a list of "followers" (users who follow that user) and another list of "following" (users that user is following). The relationship is defined in a separate table called followers which contains columns for the followed user's ID (user_id) and the following user's ID (follower_id).

My users model looks like this:

@Entity
@Table(name = "users")
@NamedQuery(name = "User.findAll", query = "SELECT u FROM USER u")
public class User {
    // other attributes
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "follower", joinColumns = @JoinColumn(
        name = "user_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(
        name = "follower_id", referencedColumnName = "id"))
    private List<User> followers;

    @ManyToMany(mappedBy = "followers")
    private List<User> following;

    // other getters and setters
    public List<User> getFollowers() {
        return this.followers;
    }

    public List<User> getFollowing() {
        return this.following;
    }
}

The getFollowers() method seems to work fine, but when getFollowing() is called I get a bunch of console spam that culminates in a StackOverflowException:

com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion 
(StackOverflowError) (through reference chain: 
org.eclipse.persistence.indirection.IndirectList[0]-
>org.myproject.model.User["followers"]-
>org.eclipse.persistence.indirection.IndirectList[0]-
>org.myproject.model.User["following"]-
...
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase
.serializeFields(BeanSerializerBase.java:518)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize
(BeanSerializer.java:117)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer
.serializeContents(IndexedListSerializer.java:94)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer
.serializeContents(IndexedListSerializer.java:21)
...

Please let me know if I should provide more of the stack trace. Any hints?

Hedwig answered 18/4, 2017 at 20:11 Comment(4)
@JacksonIgnore for your collection should solve your problemNatalyanataniel
Indeed it did (assuming you meant @JsonIgnore). You're a lifesaver!Hedwig
Can you deserialize without losing information?Prince
I won't be needing to send information about the followers through JSON so losing that information won't be an issue for me.Hedwig
N
19

Every time you have @OneToMany (a collection) you need to add @JsonIgnore to it or else it will cause an infinite loop which results in a stack overflow exception because it keeps looking up between the parent(the one side) and the child (the many side) For more info on dealing with this kind of problems check this excellent article http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

Natalyanataniel answered 18/4, 2017 at 20:32 Comment(1)
For Many To Many Relations @JsonIdentityInfo works fine.Breathed
A
1

I tried everything what I found in the web and it didn't work. None of any annotation. But I found a solution after a huge fight with this issue.

First point you need to add to both entities (not the relational one) this annotation:

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "relationClass"})
public class YourClass { ...
}

Where "relationClass" is the name of the List/Set of your class for the relations:

For example:

  @OneToMany(mappedBy = "yourClass", cascade = CascadeType.ALL, fetch = FetchType.LAZY,  orphanRemoval = true)
    private Set<RelationClass> relationClass;

You also need to specify "hibernateLazyInitializer", "handler" in the annotation or it will cause serialization problem.

After it if you see the table that define relation table and you will see rows in there, and in your JSON response will not be any loop anymore. So as I think the best solution is also create a repository for the relation tablee and access the data from there.

Hope it will help someone!

Altdorfer answered 17/10, 2019 at 2:52 Comment(0)
C
1
public class A {
  private String name;
  
  @JsonIgnoreProperties(value = "linkToA") // remove field to avoid loop
  private B linkToB;
}

public class B {
  private String name;
  
  @JsonIgnoreProperties(value = "linkToB") // remove field to avoid loop
  private A linkToA;
}
Concatenate answered 9/1, 2022 at 7:46 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Vexatious
D
0

I think the problem in previous answers is the package of the annotation. In the article http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion @JsonManagedReference and @JsonBackReference works fine.

But the package should be com.fasterxml.jackson.annotation .

Sometimes another packages may be imported and does not solve the problem.

In addition adding

import com.fasterxml.jackson.annotation.JsonIdentityInfo;

@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")

before the class definition of the model works fine.

Dody answered 20/9, 2021 at 9:13 Comment(0)
E
0

You can also use lombok's EqualsAndHashcode annotation like below.

@EqualsAndHashcode(exclude ={"following"}) public class User{}

Expellee answered 3/6, 2022 at 11:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.