N + 1 problem and solution

* The N + 1 select problem is a performance issue in Hibernate. In this problem, a Java application makes N + 1 database calls (N = number of child objects fetched). For example, if N = 2, the application makes 3 (N + 1 = 3) database calls.


package com.example.cascade_types_and_n_plus_one_problem.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.Hibernate;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Employee> employees = new ArrayList<>();

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Department that = (Department) o;
return id != null && Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}


package com.example.cascade_types_and_n_plus_one_problem.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.Hibernate;

import java.util.Objects;

@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@ToString.Exclude
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Employee employee = (Employee) o;
return id != null && Objects.equals(id, employee.id);
}

@Override
public int hashCode() {
return id.hashCode();
}

}  

Burada biz butun Department-leri bazadan ceksek, bu zaman bir sorgu (select * from deparment) generate olacaq. Departmentleri getirdikden sonra mes (3 Department geldi) department sayi qederde sorgu generate olunacaq ki Employee-leri getirsin (select * from employee where department id = 1, 2, 3 ... n). 

Ona gore de buna n + 1 problemi deyilir.

Start-------------------------------------------

Hibernate: select d1_0.id,d1_0.name from department d1_0

Hibernate: select e1_0.department_id,e1_0.id,e1_0.name from employee e1_0 where e1_0.department_id=?

Hibernate: select e1_0.department_id,e1_0.id,e1_0.name from employee e1_0 where e1_0.department_id=?

Hibernate: select e1_0.department_id,e1_0.id,e1_0.name from employee e1_0 where e1_0.department_id=?

[Department(id=1, name=Sales, employees=[Employee(id=2, name=Ilkin), Employee(id=1, name=Ali)]), Department(id=2, name=Inventory, employees=[Employee(id=3, name=Shamil)]), Department(id=3, name=Order, employees=[Employee(id=4, name=Parvin)])]

Finish------------------------------------------


Solution1: Spring Data JPA Approach - using left join fetch - JOIN FETCH:

package com.example.cascade_types_and_n_plus_one_problem.repository;

import com.example.cascade_types_and_n_plus_one_problem.entity.Course;
import com.example.cascade_types_and_n_plus_one_problem.entity.Department;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Set;

public interface DepartmentRepository extends JpaRepository<Department, Long> {

@Query("select d from Department d join fetch d.employees")
List<Department> findWithoutNPlusOne();
}

burada artiq Hibernate terfinden generate olan query:

Start-------------------------------------------

Hibernate: select d1_0.id,e1_0.department_id,e1_0.id,e1_0.name,d1_0.name from department d1_0 join employee e1_0 on d1_0.id=e1_0.department_id

[Department(id=2, name=Inventory, employees=[Employee(id=3, name=Shamil)]), Department(id=1, name=Sales, employees=[Employee(id=2, name=Ilkin), Employee(id=1, name=Ali)]), Department(id=3, name=Order, employees=[Employee(id=4, name=Parvin)])]

Finish------------------------------------------


SQL terefde bunun alternativi: 

select d.*, e.* from department d left join employee e on d.id = e.department_id;


Solution2: NamedEntityGraph

package com.example.cascade_types_and_n_plus_one_problem.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.Hibernate;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
@NamedEntityGraph(name = "department-employees",
attributeNodes = {@NamedAttributeNode("employees")})
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Employee> employees = new ArrayList<>();

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Department that = (Department) o;
return id != null && Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}
package com.example.cascade_types_and_n_plus_one_problem.repository;

import com.example.cascade_types_and_n_plus_one_problem.entity.Course;
import com.example.cascade_types_and_n_plus_one_problem.entity.Department;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Set;

public interface DepartmentRepository extends JpaRepository<Department, Long> {

@Query("select d from Department d join fetch d.employees")
List<Department> findWithoutNPlusOne();

@EntityGraph(type = EntityGraph.EntityGraphType.FETCH, value = "department-employees")
List<Department> getAllBy();
}


a) EntityGraphType.Feth - o demekdirki @NamedAttributeNode-un icinde yazilani EAGER getir, qalanlarini LAZY getir. Hetta uzerinda EAGER yazsaq bene no session xetasi atacaq.

package com.example.cascade_types_and_n_plus_one_problem.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.Hibernate;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
@NamedEntityGraph(name = "department-employees",
attributeNodes = {@NamedAttributeNode("employees")})
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Employee> employees = new ArrayList<>();

@OneToMany(mappedBy = "department", fetch = FetchType.EAGER)
private List<Car> cars = new ArrayList<>();

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
Department that = (Department) o;
return id != null && Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}


b) EntityGraphType.Load - o demekdir ki, @NamedAttributeNode-un icinde yazilani EAGER getir, qalanlari ise nece yazilibsa elede getir.












Комментарии

Популярные сообщения из этого блога

IoC:ApplicationContext, BeanFactory. Bean

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java