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:
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.
Комментарии
Отправить комментарий