Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

Sunday, January 15, 2017

Many to many relation with hibernate and json never ending recursion problem

Problem

Most examples of many-to-many relation for Spring found on the internet had a list of objects on both sides of the relation, but one of those sides was the owner of the relation. This gives us for example a list of books for a user, but we can not display a list of users for a certain book.

We were required to get a list of opposite objects on both sides (for example to get alist of books for a user, but also a list of users for one book).

On Hibernate level it wasn't a problem. Problem appeared when Jackson parser tried to change that kind of object to Json. For a linked object we obtained a neverending recursion ("user" contains a list of books, which contains a list of users, which contains a list of books etc...).

Implementation

You can find a working example-project on GitHub here:

https://github.com/bartoszkomin/hibernate-many-to-many-demo/



An example structure of the database looks as follows:

At first we've defined the entity objects in a following manner:

1:  package com.blogspot.bartoszkomin.hibernate_many_to_many_demo.model;  
2:    
3:  import java.util.List;  
4:  import javax.persistence.CascadeType;  
5:  import javax.persistence.Column;  
6:  import javax.persistence.Entity;  
7:  import javax.persistence.GeneratedValue;  
8:  import javax.persistence.GenerationType;  
9:  import javax.persistence.Id;  
10:  import javax.persistence.JoinColumn;  
11:  import javax.persistence.JoinTable;  
12:  import javax.persistence.ManyToMany;  
13:  import javax.persistence.Table;  
14:  import javax.persistence.UniqueConstraint;  
15:  import javax.validation.constraints.NotNull;  
16:    
17:  import com.fasterxml.jackson.annotation.JsonIgnoreProperties;  
18:  import com.fasterxml.jackson.annotation.JsonManagedReference;  
19:    
20:  /**  
21:   * @author Bartosz Komin  
22:   * User entity  
23:   */  
24:  @Entity  
25:  @Table(name = "users")  
26:  public class User {  
27:    
28:        
29:      @Id  
30:      @GeneratedValue(strategy = GenerationType.IDENTITY)  
31:      private Integer id;  
32:        
33:      @NotNull  
34:      @Column(nullable = false)  
35:      private String name;  
36:    
37:      @ManyToMany(cascade = {CascadeType.MERGE})  
38:      @JoinTable(name = "UserBook",   
39:          joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),   
40:          inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),   
41:          uniqueConstraints={@UniqueConstraint(columnNames={"user_id", "book_id"})})  
42:      private List<Book> books;  
43:        
44:        
45:    
46:      public String getName() {  
47:          return name;  
48:      }  
49:    
50:      public void setName(String name) {  
51:          this.name = name;  
52:      }  
53:    
54:      public List<Book> getBooks() {  
55:          return books;  
56:      }  
57:    
58:      public void setBooks(List<Book> books) {  
59:          this.books = books;  
60:      }  
61:    
62:      public Integer getId() {  
63:          return id;  
64:      }  
65:        
66:  }  

1:  package com.blogspot.bartoszkomin.hibernate_many_to_many_demo.model;  
2:    
3:  import java.util.List;  
4:  import javax.persistence.CascadeType;  
5:  import javax.persistence.Column;  
6:  import javax.persistence.Entity;  
7:  import javax.persistence.GeneratedValue;  
8:  import javax.persistence.GenerationType;  
9:  import javax.persistence.Id;  
10:  import javax.persistence.JoinColumn;  
11:  import javax.persistence.JoinTable;  
12:  import javax.persistence.ManyToMany;  
13:  import javax.persistence.Table;  
14:  import javax.persistence.UniqueConstraint;  
15:  import javax.validation.constraints.NotNull;  
16:    
17:  import com.fasterxml.jackson.annotation.JsonBackReference;  
18:  import com.fasterxml.jackson.annotation.JsonIgnore;  
19:  import com.fasterxml.jackson.annotation.JsonIgnoreProperties;  
20:    
21:  /**  
22:   * @author Bartosz Komin  
23:   * Book entity  
24:   */  
25:  @Entity  
26:  @Table(name = "books")  
27:  public class Book {  
28:    
29:        
30:      @Id  
31:      @GeneratedValue(strategy = GenerationType.IDENTITY)  
32:      private Integer id;  
33:        
34:      @NotNull  
35:      @Column(nullable = false)  
36:      private String name;  
37:    
38:      @ManyToMany(cascade = {CascadeType.MERGE})  
39:      @JoinTable(name = "UserBook",   
40:          joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),   
41:          inverseJoinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),   
42:          uniqueConstraints={@UniqueConstraint(columnNames={"book_id", "user_id"})})  
43:      private List<User> users;  
44:        
45:      public String getName() {  
46:          return name;  
47:      }  
48:    
49:      public void setName(String name) {  
50:          this.name = name;  
51:      }  
52:    
53:      public List<User> getUsers() {  
54:          return users;  
55:      }  
56:    
57:      public void setUsers(List<User> users) {  
58:          this.users = users;  
59:      }  
60:    
61:      public Integer getId() {  
62:          return id;  
63:      }  
64:        
65:  }  

In the presented GitHub project we can find a few REST endpoints:

POST /users
GET /users
GET /users/user_id
POST /books
GET /books
GET /books/book_id

With presented entities it works properly on Database/Hibernate level. But if we want to obtain some linked objects, we get a neverending recursion, which occurs on Jackson level, and it looks like this:

{"id":1,"name":"Albert Einstein","books":[{"id":1,"name":"The Meaning of Relativity", "users":[{"id":1,"name":"Albert Einstein","books":[{"id":1,"name":"The Meaning of Relativity", "users":{...}}]}]}]}

Solution
I've found some tips on the internet with an @JsonIgnore or @JsonManagedReference + @JsonBackReference annotation, but it was not working in our case (we were able to see the list only on one side).

Our solution was to use @JsonIgnoreProperties annotation as follows:

User class
1:  @ManyToMany(cascade = {CascadeType.MERGE})  
2:  @JoinTable(name = "UserBook",   
3:        joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),   
4:        inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),   
5:        uniqueConstraints={@UniqueConstraint(columnNames={"user_id", "book_id"})})  
6:  @JsonIgnoreProperties("users")  
7:  private List<Book> books;  

Book class
1:  @ManyToMany(cascade = {CascadeType.MERGE})  
2:  @JoinTable(name = "UserBook",   
3:        joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),   
4:        inverseJoinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),   
5:        uniqueConstraints={@UniqueConstraint(columnNames={"book_id", "user_id"})})  
6:  @JsonIgnoreProperties("books")  
7:  private List<User> users;  

Working example result:

GET /users/1

GET /books/1


Summary

In the GitHub demo project you can find a working example in master branch.

There is also one more branch: many-to-many-recursion-problem, where you can check how application behaves, when neverending recursion appears. My IDE got laggy, and that might cause a problem with getting a response. I removed tests from this branch to not overload the computer when trying to build a project.

On GitHub "readme" there is also some information on how to run the project and how to send a request to api with curl.