I understand that the N+1 problem is where one query is executed to fetch N records and N queries to fetch some relational records.
But how can it be avoided in Hibernate?
I understand that the N+1 problem is where one query is executed to fetch N records and N queries to fetch some relational records.
But how can it be avoided in Hibernate?
 
    
     
    
    The N+1 query issue happens when you forget to fetch an association and then you need to access it.
For instance, let's assume we have the following JPA query:
List<PostComment> comments = entityManager.createQuery("""
    select pc
    from PostComment pc
    where pc.review = :review
    """, PostComment.class)
.setParameter("review", review)
.getResultList();
Now, if we iterate the PostComment entities and traverse the post association:
for(PostComment comment : comments) {
    LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}
Hibernate will generate the following SQL statements:
SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM   post_comment pc
WHERE  pc.review = 'Excellent!'
INFO - Loaded 3 comments
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 1
INFO - The post title is 'Post nr. 1'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 2
INFO - The post title is 'Post nr. 2'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM   post pc
WHERE  pc.id = 3
INFO - The post title is 'Post nr. 3'
That's how the N+1 query issue is generated.
Because the post association is not initialized when fetching the PostComment entities, Hibernate must fetch the Post entity with a secondary query, and for N PostComment entities, N more queries are going to be executed (hence the N+1 query problem).
The first thing you need to do to tackle this issue is to add proper SQL logging and monitoring. Without logging, you won't notice the N+1 query issue while developing a certain feature.
Second, to fix it, you can just JOIN FETCH the relationship causing this issue:
List<PostComment> comments = entityManager.createQuery("""
    select pc
    from PostComment pc
    join fetch pc.post p
    where pc.review = :review
    """, PostComment.class)
.setParameter("review", review)
.getResultList();
If you need to fetch multiple child associations, it's better to fetch one collection in the initial query and the second one with a secondary SQL query.
This issue is better to be caught by integration tests.
You can use an automatic JUnit assert to validate the expected count of generated SQL statements. The db-util project already provides this functionality, and it's open-source and the dependency is available on Maven Central.
 
    
     
    
    Suppose we have a class Manufacturer with a many-to-one relationship with Contact.
We solve this problem by making sure that the initial query fetches all the data needed to load the objects we need in their appropriately initialized state. One way of doing this is using an HQL fetch join. We use the HQL
"from Manufacturer manufacturer join fetch manufacturer.contact contact"
with the fetch statement. This results in an inner join:
select MANUFACTURER.id from manufacturer and contact ... from 
MANUFACTURER inner join CONTACT on MANUFACTURER.CONTACT_ID=CONTACT.id
Using a Criteria query we can get the same result from
Criteria criteria = session.createCriteria(Manufacturer.class);
criteria.setFetchMode("contact", FetchMode.EAGER);
which creates the SQL :
select MANUFACTURER.id from MANUFACTURER left outer join CONTACT on 
MANUFACTURER.CONTACT_ID=CONTACT.id where 1=1
in both cases, our query returns a list of Manufacturer objects with the contact initialized. Only one query needs to be run to return all the contact and manufacturer information required
for further information here is a link to the problem and the solution.
 
    
    Native solution for 1 + N in Hibernate, is called:
Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways we can configure batch fetching: on the 1) class level and the 2) collection level...
Check these Q & A:
With annotations we can do it like this:
A class level:
@Entity
@BatchSize(size=25)
@Table(...
public class MyEntity implements java.io.Serializable {...
A collection level:
@OneToMany(fetch = FetchType.LAZY...)
@BatchSize(size=25)
public Set<MyEntity> getMyColl() 
Lazy loading and batch fetching together represent optimization, which:
 
    
     
    
    If you are using Spring Data JPA to implement your repositories, you can specify lazy fetching in the JPA associations:
@Entity
@Table(name = "film", schema = "public")
public class Film implements Serializable {
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "language_id", nullable = false)
  private Language language;
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "film")
  private Set<FilmActor> filmActors;
...
}
@Entity
@Table(name = "film_actor", schema = "public")
public class FilmActor implements Serializable {
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "film_id", nullable = false, insertable = false, updatable = false)
  private Film film;
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "actor_id", nullable = false, insertable = false, updatable = false)
  private Actor actor;
...
}
@Entity
@Table(name = "actor", schema = "public")
public class Actor implements Serializable {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "actor")
  private Set<FilmActor> filmActors;
...
}
And add @EntityGraph to your Spring Data JPA-based repository:
@Repository
public interface FilmDao extends JpaRepository<Film, Integer> {
  @EntityGraph(
    type = EntityGraphType.FETCH,
    attributePaths = {
      "language",
      "filmActors",
      "filmActors.actor"
    }
  )
  Page<Film> findAll(Pageable pageable);
...
}
My blog post at https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html helps you preventing the N+1 select problem using Spring Data JPA and @EntityGraph.
 
    
    You can even get it working without having to add the @BatchSize annotation everywhere, just set the property hibernate.default_batch_fetch_size to the desired value to enable batch fetching globally. See the  Hibernate docs for details.
While you are at it, you will probably also want to change the BatchFetchStyle, because the default (LEGACY) is most likely not what you want. So a complete configuration for globally enabling batch fetching would look like this:
hibernate.batch_fetch_style=PADDED
hibernate.default_batch_fetch_size=25
Also, I'm suprised that one of the proposed solutions involves join-fetching. Join-fetching is rarely desirable because it causes more data to be transferred with every result row, even if the dependent entity has already been loaded into the L1 or L2 cache. Thus I would recommend to disable it completey by setting
hibernate.max_fetch_depth=0
 
    
    This is a frequently asked question so I created the article Eliminate Spring Hibernate N+1 Queries to detail the solutions
To help you detect all the N+1 queries in your application and avoid adding more queries, I created the library spring-hibernate-query-utils that auto-detects the Hibernate N+1 queries.
Here is some code to explain how to add it to your application:
<dependency>
    <groupId>com.yannbriancon</groupId>
    <artifactId>spring-hibernate-query-utils</artifactId>
    <version>1.0.3</version>
</dependency>
hibernate.query.interceptor.error-level=EXCEPTION
Here are some snippet codes that would help you to fix the N+1 Problem.
One to Many Relationship with Manager and Client Entity.
Client JPA Repository -
public interface ClientDetailsRepository extends JpaRepository<ClientEntity, Long> {
    @Query("FROM clientMaster c join fetch c.manager m where m.managerId= :managerId")
    List<ClientEntity> findClientByManagerId(String managerId);
}
Manager Entity -
@Entity(name = "portfolioManager")
@Table(name = "portfolio_manager")
public class ManagerEntity implements Serializable {
      // some fields
@OneToMany(fetch = FetchType.LAZY, mappedBy = "manager")
protected List<ClientEntity> clients = new ArrayList<>();
     // Getter & Setter 
}
Client Entity -
@Entity(name = "clientMaster")
@Table(name = "clientMaster")
public class ClientEntity implements Serializable {
    // some fields
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "manager_id", insertable = false, updatable = false)
    protected ManagerEntity manager;
    // Getter & Setter 
 }
And finally, Generate output -
Hibernate: select cliententi0_.client_id as client_id1_0_0_, cliententi0_.manager_id as manager_id2_0_0_, managerent1_.manager_id as manager_id1_2_1_, cliententi0_.created_by as created_by7_0_0_, cliententi0_.created_date as created_date3_0_0_, cliententi0_.client_name as client_name4_0_0_, cliententi0_.sector_name as sector_name5_0_0_, cliententi0_.updated_by as updated_by8_0_0_, cliententi0_.updated_date as updated_date6_0_0_, managerent1_.manager_name as manager_name2_2_1_ from client_master cliententi0_, portfolio_manager managerent1_ where cliententi0_.manager_id=managerent1_.manager_id and managerent1_.manager_id=?```
 
    
    One of the best solution is to use @Fetch(FetchMode.SUBSELECT) fetch model. Below is a sample illustration.
Customer Class
@Entity
@Table(name ="customer_table")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int customerId;
    private String name;
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    @Fetch(FetchMode.SUBSELECT)
    private List<Order> orderList;
    
   @Override
   public String toString() {
      return String.format("customerId %d", customerId);
   }
}
Order Class
@Entity
@Table(name = "order_table")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int orderId;
    private double amount;
    public Order(double amount) {
        this.amount = amount;
    }
    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_Id")
    private Customer customer;
}
This will trigger below hql query for fetching customer with id 1,2
customerRepo.findAllById(List.of(1, 2));
   select
        o1_0.customer_id,
        o1_0.order_id,
        o1_0.amount 
    from
        order_table o1_0 
    where
        o1_0.customer_id in(select
            c1_0.customer_id 
        from
            customer_table c1_0 
        where
            c1_0.customer_id in (?,?))
 binding parameter [1] as [INTEGER] - [1]
 binding parameter [2] as [INTEGER] - [2]
Credit: appmap
