Hibernate @OneToMany Relationship Causes Infinite Loop Or Empty Entries in JSON Result
Asked Answered
E

10

29

I have two entities, an entity "movie" and an entity "Clip" each clip belongs to one movie and a movie can have multiple clips.

My code looks like:

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();


  
 Clip.java
    
    @ManyToOne
        @JoinColumn(name="movie_id")
        private Movie movie;

The tables are being generated and each Clip has a column "movie_id" but this causes my application to end up in an infinite loop when I'm requesting Data

    @Path("/{id:[0-9][0-9]*}")
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Movie lookupMovieById(@PathParam("id") long id) {
            return em.find(Movie.class, id);
        }
    

result:
{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"MGS Walkthrough P1","keywords":null,"movie":{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"MGS Walkthrough P1","keywords":null,"movie":{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"M...

It's the same result when I'm requesting a clip.

When I change it to a @ManyToMany relationship there won't be any problems like that, but that's not what I need here. Can you help me? Setting fetchType to Lazy didn't work.

Edit: I'm using the current JBoss development studio

Edit:

I "solved" this, by reading this article:

https://web.archive.org/web/20180401022123/http://blog.jonasbandi.net/2009/02/help-needed-mapping-bidirectional-list.html

"To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and set the many to one @JoinColumn as insertable and updatable to false. This solution is obviously not optimized and will produce some additional UPDATE statements."

when I request a movie I get the following answer:

{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[],"description":"Trailer zu mgs4"}

the entry "clips" still appears. Is this still the wrong solution or do I just have to live with this?

Evin answered 16/5, 2013 at 2:10 Comment(2)
link is not workingLysin
@Lysin I updated the link to an archived version of the blog... web.archive.org/web/20180401022123/http://blog.jonasbandi.net/…Evin
D
22

I ran into exactly the same problem. I tried the solution from the quoted paragraph, it did not work for me.

What I did is to return null for getMovie() in Clip class, then the infinite loop issue is gone. The data returned in JSON format will look like {"movieId":1 ... clips:["clipId":1, "movie":"null", ..]}.

If you also want further remove the movie property in JSON, add the class-level annotation to Clip class @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)

Jackson feature: prevent serialization of nulls, default values

Update: The easier way I found is to simply remove the getter of movie in Clip class.

Dink answered 8/8, 2013 at 19:53 Comment(6)
I don't quite understand the point of this, if we don't need the set/list returned or JSON returned, why are we doing all these complicated relationship mappings? just to update the join table?Increasing
@JohnSmith it's been a while since I posted my answer here, but I remember it was a legacy system that was not allowed to change the schema.Dink
Thank you..It really helped me.Jermyn
Thank you bro. It drove me crazy with this problem.Cringle
with Jackson 2.x you can use @JsonInclude(JsonInclude.Include.NON_NULL)Dinahdinan
What if I need the getter method later?Jessamyn
C
71

Solution:

Use

@JsonManagedReference annotation for the first objects instantiated

@JsonBackReference annotation for the second objects instantiated

Movie.java

@JsonManagedReference
@OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();

Clip.java

@JsonBackReference
@ManyToOne
    @JoinColumn(name="movie_id")
    private Movie movie;
Cordwain answered 5/11, 2017 at 5:14 Comment(6)
great solution, this is exactly the way how it should be done, not like others here, hacky onesNervine
THIS IS THE CORRECT SOLUTION. EVERYONE SHOULD TRY THIS SOLUTION FIRST. This problem plagued me, I couldn't solve it. Gave up for a couple months. Tried again, found this answer and it worked!Sunderland
It is the correct answer. The others are work around.Isoclinal
with JsonManagedReference FetchType.Eager is okay or Lazy ?Peacock
These annotations are not available to me. Care to explain where they came from?Scramble
this was usefulBowhead
D
22

I ran into exactly the same problem. I tried the solution from the quoted paragraph, it did not work for me.

What I did is to return null for getMovie() in Clip class, then the infinite loop issue is gone. The data returned in JSON format will look like {"movieId":1 ... clips:["clipId":1, "movie":"null", ..]}.

If you also want further remove the movie property in JSON, add the class-level annotation to Clip class @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)

Jackson feature: prevent serialization of nulls, default values

Update: The easier way I found is to simply remove the getter of movie in Clip class.

Dink answered 8/8, 2013 at 19:53 Comment(6)
I don't quite understand the point of this, if we don't need the set/list returned or JSON returned, why are we doing all these complicated relationship mappings? just to update the join table?Increasing
@JohnSmith it's been a while since I posted my answer here, but I remember it was a legacy system that was not allowed to change the schema.Dink
Thank you..It really helped me.Jermyn
Thank you bro. It drove me crazy with this problem.Cringle
with Jackson 2.x you can use @JsonInclude(JsonInclude.Include.NON_NULL)Dinahdinan
What if I need the getter method later?Jessamyn
F
10

First, let me show you why setting fetch type to lazy doesn't help. When you try to serialize your pojo, your serializer (maybe jackson) would call every getter of this pojo, and use the returned value of getter as the properties in json data. So it calls the getter explicitly, which calls hibernate to load your associated entities ( movie for Clip and clips for Movie ). So you need to use @JsonIgnoreProperties to get rid of this weird infinite loop, the code goes like this:

Clip.java

    @ManyToOne
    @JoinColumn(name="movie_id")
    @JsonIgnoreProperties("clips")
    private Movie movie;

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JsonIgnoreProperties("movie")
    private Set<Clip> clips = new HashSet<Clip>();

That way, you would find the nested movie in clip json object has no "clips" inside movie, and the nested clips in movie has no "movie" child neither.

I guess this is the best way to deal with this problem, and also the best practice to develop java web app.

Floor answered 20/5, 2019 at 12:12 Comment(3)
I kept using @JsonIgnoreProperties and that wasn't doing the trick. Used @JsonIgnoreProperties("clips") instead and it works like a charm. Thanks a lot!! :DCanzone
This and remove @Data of Lombok works for meHallerson
This is the best answer, saved my day :))Destructible
V
2

Instead of returning an Entity object, I suggest return a DTO object only with the data you need. You can get one directly from a Hibernate query/criteria results, using result transformers.

Voletta answered 20/9, 2013 at 16:42 Comment(0)
P
2

As you are saying "the entry clips still appears".

To avoid the relationship data in the db response change fetch = FetchType.EAGER to fetch = FetchType.Lazy.

Pyorrhea answered 19/10, 2015 at 11:27 Comment(0)
D
2

Need to add @JsonIgnore in child class to avoid such exception. Be careful not to add this annotation in Parent class

@Entity  
@Table(name="Movie")
public class Movie implements Serializable{

@OneToMany(mappedBy="movie",targetEntity=Clip.class,cascade=CascadeType.ALL,
fetch=FetchType.EAGER)
private Set<Clip> clips = new HashSet<Clip>();

}

@Entity  
@Table(name="Clip")
public class Clip implements Serializable{   
    @ManyToOne
    @JoinColumn(name="movie_id")
    @JsonIgnore
    private Movie movie;
}
Draughts answered 21/10, 2018 at 21:2 Comment(0)
D
2

I got the main problem of this situation.

When you get Movie, system will load a list of relation Clips, but in CLip class you have a property Movie, when you have getter of this property, system will load Movie again.

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();

//the below getter will look for data of related Clip
    public Set<Clip> getClips(){ return this.clips}


 Clip.java

    @ManyToOne
        @JoinColumn(name="movie_id")
        private Movie movie;

//the below getter will look for related Movie again
   public Movie getMovie() { return this.movie }

For example: You get Movie 01, this movie has relation with Clip 01 and Clip 02, when system load data of Movie 01 it also fetch data of Clip 01 and Clip 02 via your getter method.

But in Clip class you also has property Movie and a getter getMovie(). So when system looks for data of CLip 01, it also get data of relation Movie, in this situation is Movie 01... and Movie 01 will get data of CLip 01 => So this is exactly reason why we have a loop

So that the exactly solution for this situation is Delete Getter Method getMovie() in Clip.java we dont need to use this information

Dispensatory answered 22/4, 2019 at 16:31 Comment(0)
C
1

Basically JsonIgnore or JsonBackrefrencing will remove linking from one end.

To make proper linking as well as correct Json output, please follow below code snippet :-

@Entity  
@Table(name="Movie")
public class Movie{

@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="@id")
@OneToMany(mappedBy="movie",cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Set<Clip> clips = new HashSet<Clip>();

}


@Entity  
@Table(name="Clip")
public class Clip{ 

@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="@id")  
@ManyToOne
@JoinColumn(name="movie_id")
@JsonIgnore
private Movie movie;
}

refer below link for further details :- https://www.toptal.com/javascript/bidirectional-relationship-in-json

import com.fasterxml.jackson.annotation.ObjectIdGenerators;
Chuck answered 26/11, 2019 at 18:16 Comment(0)
M
0

Easy way as Dino Tw said:

Remove the getMovie() function from the Clip class.

That will help you to retrieve the Clip list for each Movie which is associated by movie_id in the Clip entity/table.

Montespan answered 1/6, 2018 at 15:9 Comment(1)
This is not a new answer, please add this text as it should be, as a comment to the other answer.Laney
N
0

If you are returning nested relationships, you will also get this error if you are returning the response as a List rather than Iterable.

Nikkinikkie answered 10/4 at 3:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.