@Entity
@Table(name = "t_users")
public class User {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String name;
    @ManyToMany(fetch = FetchType.LAZY)                   (1)
    private Set<Role> roles;
    //Getter and Setters removed for brevity
 }
@Entity
@Table(name = "t_roles")
public class Role {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    private String name;
    //Getter and Setters removed for brevity
 }N+1 problem in Hibernate & Spring Data JPA
Upasana | October 03, 2020 | 4 min read | 3,088 views
N+1 problem is a performance issue in Object Relational Mapping that fires multiple select queries (N+1 to be exact, where N = number of records in table) in database for a single select query at application layer. Hibernate & Spring Data JPA provides multiple ways to catch and address this performance problem.
What is N+1 Problem?
To understand N+1 problem, lets consider a scenario. Let’s say we have a collection of User objects mapped to t_users table in database, and each user has collection or Role mapped to t_roles table using a joining table t_user_roles. At the ORM level a User has many to many relationship with Role.
| 1 | A user can have many roles. Roles are loaded Lazily. | 
Now lets say we want to fetch all users from this table and print roles for each one. Very naive Object Relational implementation could be -
public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findAllBy();
}Equivalent SQL queries executed by ORM will be:
First Get All User (1)
Select * from t_users;
Then get roles for each user executed N times (where N is number of users)
Select * from t_user_roles where userid = <userid>;
So we need one select for User and N additional selects for fetching roles for each user, where N is total number of users. This is a classic N+1 problem in ORM.
How to identify it?
Hibernate provide tracing option that enables SQL logging in the console/logs. using logs you can easily see if hibernate is issuing N+1 queries for a given call.
spring:
    jpa:
       show-sql: true             (1)
       database-platform: org.hibernate.dialect.MySQL8Dialect
       hibernate:
         ddl-auto: create
         use-new-id-generator-mappings: true
       properties:
          hibernate:
            type: trace            (2)| 1 | Enables SQL logging in trace. | 
| 2 | We have to enable this too in order to show sql queries in logs. | 
2017-12-23 07:42:30.923  INFO 11657 --- [           main] hello.UserService                        : Customers found with findAll():
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_ from user user0_
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?If you see multiple entries for SQL for a given select query, then there are high chances that its due to N+1 problem.
N+1 Resolution
Hibernate & Spring Data JPA provide mechanism to solve the N+1 ORM issue.
At SQL level, what ORM needs to achieve to avoid N+1 is to fire a query that joins the two tables and get the combined results in single query.
Hibernate: select user0_.id as id1_1_0_, role2_.id as id1_0_1_, user0_.name as name2_1_0_, role2_.name as name2_0_1_, roles1_.user_id as user_id1_2_0__, roles1_.roles_id as roles_id2_2_0__ from user user0_ left outer join user_roles roles1_ on user0_.id=roles1_.user_id left outer join role role2_ on roles1_.roles_id=role2_.idselect user0_.id, role2_.id, user0_.name, role2_.name, roles1_.user_id, roles1_.roles_id from user user0_ left outer join user_roles roles1_ on user0_.id=roles1_.user_id left outer join role role2_ on roles1_.roles_id=role2_.id
1. Spring Data JPA Approach
If we are using Spring Data JPA, then we have two options to achieve this - using EntityGraph or using select query with fetch join.
public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findAllBy();             (1)
    @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles")  (2)
    List<User> findWithoutNPlusOne();
    @EntityGraph(attributePaths = {"roles"})                (3)
    List<User> findAll();
}| 1 | N+1 queries are issued at database level | 
| 2 | using left join fetch, we resolve the N+1 problem | 
| 3 | using attributePaths, Spring Data JPA avoids N+1 problem | 
2. Hibernate Approach
If its pure Hibernate, then the following solutions will work.
"from User u join fetch u.roles roles roles"
Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("roles", FetchMode.EAGER);under the hood, all these approaches work similar and they issue a similar database query with left join fetch
That’s all!
Top articles in this category:
- Prevent Lost Updates in Database Transaction using Spring Hibernate
- What are inheritance mapping strategies in JPA
- Table backed global counter in spring hibernate
- Redis rate limiter in Spring Boot
- Disable SSL validation in Spring RestTemplate
- Custom TTL for Spring data Redis Cache
- Spring Data ElasticSearch with Basic Auth
 
                        