Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 650
Goot Avatar asked May 16 '13 02:05

Goot


4 Answers

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;
like image 63
btd1337 Avatar answered Nov 13 '22 19:11

btd1337


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.

like image 23
Dino Tw Avatar answered Nov 13 '22 19:11

Dino Tw


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.

like image 9
Dai Niu Avatar answered Nov 13 '22 20:11

Dai Niu


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.

like image 2
rvazquezglez Avatar answered Nov 13 '22 20:11

rvazquezglez