In Hibernate/JPA we face N+1 Problem. First we will see what is the N+1 Problem.
In order to find, what is N+1 Problem Please go through the below stackoverflow link
We can fix N+1 select problem by using Entity Graphs provided by JPA 2.1. we can achieve it in the following ways.
- Using Entity Manager
- Using JPA Repository
- Using 3rd Party Entity Graph Utility
graph TD A[Entity Graph] -->|Using Entity Manager Class | B(Hibernate Entity Manager ) A[Entity Graph] -->|Using JPA Repository Class | C(JPA Repository ) A[Entity Graph] -->|Using 3rd Party Entity Graph | D(3rd Party Entity Graph Utility ) In this example we will use Hibernate Entity Manager to solve N+1 problem.
graph TD A[Hibernate Entity Manager] --> | Using Named Entity Graph API | B(NamedEntityGraph) A[Hibernate Entity Manager] --> | Using Dynamically pass graph attribute nodes | C(Dynamic Entity Graph API) A[Hibernate Entity Manager] --> | Using Root Entity Graph API | D(RootGraph API) EntityManager entityManager = getEntityManager(); Author author = entityManager.find(Author.class, 1); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); } We can notice when I tried to access author, books and publisher, it issues 3 queries to fetch the data.
19:22:24,828 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_ from author author0_ where author0_.id=? 19:22:24,867 DEBUG [org.hibernate.SQL] - select books0_.author_id as author_i2_2_0_, books0_.book_id as book_id1_2_0_, book1_.id as id1_1_1_, book1_.publisher_id as publishe5_1_1_, book1_.publishing_date as publishi2_1_1_, book1_.title as title3_1_1_, book1_.version as version4_1_1_ from book_author books0_ inner join book book1_ on books0_.book_id=book1_.id where books0_.author_id=? Joshua Bloch wrote 1 books. 19:22:24,887 DEBUG [org.hibernate.SQL] - select publisher0_.id as id1_3_0_, publisher0_.name as name2_3_0_, publisher0_.version as version3_3_0_ from publisher publisher0_ where publisher0_.id=? Publisher name: Addison-Wesley Professional @Entity @Table(name = "author") @NamedEntityGraph(name = "graph.author.books.publisher", attributeNodes = @NamedAttributeNode(value = "books", subgraph = "books"), subgraphs = @NamedSubgraph(name = "books", attributeNodes = @NamedAttributeNode("publisher")) ) @NamedEntityGraph(name = "graph.author.books", attributeNodes = @NamedAttributeNode(value = "books")) public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @ManyToMany(mappedBy="authors",fetch = FetchType.LAZY) private Set<Book> books = new HashSet<Book>(); } @Entity @Table(name = "book") public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String title; @Temporal(TemporalType.DATE) @Column(name = "publishing_date") private Date publishingDate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="publisher_id") private Publisher publisher; @ManyToMany @JoinTable( name="book_author", joinColumns={@JoinColumn(name="book_id", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="author_id", referencedColumnName="id")}) private Set<Author> authors = new HashSet<Author>(); } @Entity @Table(name = "publisher") public class Publisher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String name; @OneToMany(mappedBy = "publisher") private Set<Book> books = new HashSet<Book>(); }In this example we are using @NamedEntityGraph name attribute graph.author.books to get entity graph api. It will load only Book objects but not publisher associate with books
EntityManager entityManager = getEntityManager(); String hql = "SELECT a FROM Author a WHERE a.id = 1"; EntityGraph graph = entityManager.getEntityGraph("graph.author.books"); TypedQuery<Author> query = entityManager.createQuery(hql, Author.class); query.setHint("javax.persistence.loadgraph", graph); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); }we can notice that it issued only 2 queries to fetch data. This is because we included books as part of graph.
19:27:38,472 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, book2_.publisher_id as publishe5_1_1_, book2_.publishing_date as publishi2_1_1_, book2_.title as title3_1_1_, book2_.version as version4_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id where author0_.id=1 Joshua Bloch wrote 1 books. 19:27:38,534 DEBUG [org.hibernate.SQL] - select publisher0_.id as id1_3_0_, publisher0_.name as name2_3_0_, publisher0_.version as version3_3_0_ from publisher publisher0_ where publisher0_.id=? Publisher name: Addison-Wesley Professional In this example we are using @NamedEntityGraph name attribute graph.author.books.publisher to get entity graph api. It will load Book objects as well as publisher associate with books
EntityManager entityManager = getEntityManager(); String hql = "SELECT a FROM Author a WHERE a.id = 1"; EntityGraph graph = entityManager.getEntityGraph("graph.author.books.publisher"); TypedQuery<Author> query = entityManager.createQuery(hql, Author.class); query.setHint("javax.persistence.loadgraph", graph); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); }we can notice that it issued only one query to fetch all data. This is because we included books and publisher as part of graph.
19:31:18,884 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, publisher3_.id as id1_3_2_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, book2_.publisher_id as publishe5_1_1_, book2_.publishing_date as publishi2_1_1_, book2_.title as title3_1_1_, book2_.version as version4_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__, publisher3_.name as name2_3_2_, publisher3_.version as version3_3_2_ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id left outer join publisher publisher3_ on book2_.publisher_id=publisher3_.id where author0_.id=1 Joshua Bloch wrote 1 books. Publisher name: Addison-Wesley Professional @Entity public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @ManyToMany(mappedBy="authors",fetch = FetchType.LAZY) private Set<Book> books = new HashSet<Book>(); } @Entity @Table(name = "book") public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String title; @Temporal(TemporalType.DATE) @Column(name = "publishing_date") private Date publishingDate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="publisher_id") private Publisher publisher; @ManyToMany @JoinTable( name="book_author", joinColumns={@JoinColumn(name="book_id", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="author_id", referencedColumnName="id")}) private Set<Author> authors = new HashSet<Author>(); } @Entity @Table(name = "publisher") public class Publisher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String name; @OneToMany(mappedBy = "publisher") private Set<Book> books = new HashSet<Book>(); }In this example we can set attributes dynamically. Graph API has addAttributeNodes(property name) method to set property names. We can set as many as attributesnodes to graph api. Here Author class has property name books. So we setting books attribute to graph. It will load only Book objects but not publisher associate with books
String HQL ="SELECT a FROM Author a WHERE a.id = 1"; EntityManager entityManager = getEntityManager(); EntityGraph<Author> graph = entityManager.createEntityGraph(Author.class); graph.addAttributeNodes("books"); TypedQuery<Author> query = entityManager.createQuery(HQL, Author.class); query.setHint("javax.persistence.loadgraph", graph); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); } 20:02:56,111 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, book2_.publisher_id as publishe5_1_1_, book2_.publishing_date as publishi2_1_1_, book2_.title as title3_1_1_, book2_.version as version4_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id where author0_.id=1 Joshua Bloch wrote 1 books. 20:02:56,178 DEBUG [org.hibernate.SQL] - select publisher0_.id as id1_3_0_, publisher0_.name as name2_3_0_, publisher0_.version as version3_3_0_ from publisher publisher0_ where publisher0_.id=? Publisher name: Addison-Wesley Professional Graph API provide to add sub graph and its properties also, by using addSubgraph(property name) and addAttributeNodes(property name) Author class has property name books and Book class has property name publisher It will load Book objects as well as publisher associate with books
EntityManager entityManager = getEntityManager(); String HQL ="SELECT a FROM Author a WHERE a.id = 1"; EntityGraph<Author> graph = entityManager.createEntityGraph(Author.class); graph.addSubgraph("books").addAttributeNodes("publisher"); TypedQuery<Author> query = entityManager.createQuery(HQL, Author.class); query.setHint("javax.persistence.loadgraph", graph); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); }20:03:52,435 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, publisher3_.id as id1_3_2_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, book2_.publisher_id as publishe5_1_1_, book2_.publishing_date as publishi2_1_1_, book2_.title as title3_1_1_, book2_.version as version4_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__, publisher3_.name as name2_3_2_, publisher3_.version as version3_3_2_ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id left outer join publisher publisher3_ on book2_.publisher_id=publisher3_.id where author0_.id=1 Joshua Bloch wrote 1 books. Publisher name: Addison-Wesley Professional @Entity public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @ManyToMany(mappedBy="authors",fetch = FetchType.LAZY) private Set<Book> books = new HashSet<Book>(); } @Entity @Table(name = "book") public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String title; @Temporal(TemporalType.DATE) @Column(name = "publishing_date") private Date publishingDate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="publisher_id") private Publisher publisher; @ManyToMany @JoinTable( name="book_author", joinColumns={@JoinColumn(name="book_id", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="author_id", referencedColumnName="id")}) private Set<Author> authors = new HashSet<Author>(); } @Entity @Table(name = "publisher") public class Publisher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Version private Integer version; private String name; @OneToMany(mappedBy = "publisher") private Set<Book> books = new HashSet<Book>(); }In this example we need to create RootGraph object and pass all properties to load.
In this example, it will load only Book objects but not publisher associate with books
String HQL ="SELECT a FROM Author a WHERE a.id = 1"; RootGraph<Author> graph = GraphParser.parse(Author.class, "books", entityManager); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("javax.persistence.loadgraph", graph); TypedQuery<Author> query = entityManager.createQuery(HQL, Author.class); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); }20:04:50,371 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, books1_.author_id as author_i2_2_1_, book2_.id as book_id1_2_1_, book2_.id as id1_1_2_, book2_.publisher_id as publishe5_1_2_, book2_.publishing_date as publishi2_1_2_, book2_.title as title3_1_2_, book2_.version as version4_1_2_ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id where author0_.id=? Joshua Bloch wrote 1 books. 20:04:50,424 DEBUG [org.hibernate.SQL] - select publisher0_.id as id1_3_0_, publisher0_.name as name2_3_0_, publisher0_.version as version3_3_0_ from publisher publisher0_ where publisher0_.id=? Publisher name: Addison-Wesley Professional In this example we need to create RootGraph object and pass all properties to load. Author class has property name books and Book class has property name publisher
In this example, it will load Book objects as well as publisher associate with books
String HQL ="SELECT a FROM Author a WHERE a.id = 1"; RootGraph<Author> graph = GraphParser.parse(Author.class, "books(publisher)", entityManager); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("javax.persistence.loadgraph", graph); TypedQuery<Author> query = entityManager.createQuery(HQL, Author.class); Author author = query.getSingleResult(); System.out.println(author.getFirstName()+" "+author.getLastName()+" wrote "+author.getBooks().size()+" books."); Set<Book> books = author.getBooks(); for(Book book: books) { System.out.println(book.getPublisher()); }20:05:40,676 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, author0_.first_name as first_na2_0_0_, author0_.last_name as last_nam3_0_0_, author0_.version as version4_0_0_, books1_.author_id as author_i2_2_1_, book2_.id as book_id1_2_1_, book2_.id as id1_1_2_, book2_.publisher_id as publishe5_1_2_, book2_.publishing_date as publishi2_1_2_, book2_.title as title3_1_2_, book2_.version as version4_1_2_, publisher3_.id as id1_3_3_, publisher3_.name as name2_3_3_, publisher3_.version as version3_3_3_ from author author0_ left outer join book_author books1_ on author0_.id=books1_.author_id left outer join book book2_ on books1_.book_id=book2_.id left outer join publisher publisher3_ on book2_.publisher_id=publisher3_.id where author0_.id=? Joshua Bloch wrote 1 books. Publisher name: Addison-Wesley Professional 