JPA Batch Size
1. Overview
BatchSize 는 JPA 의 성능 개선을 위한 옵션 중 하나입니다.
여러 개의 프록시 객체를 조회할 때 WHERE
절이 같은 여러 개의 SELECT
쿼리들을 하나의 IN
쿼리로 만들어줍니다.
간단한 테스트와 함께 사용법을 알아봅니다.
2. Domain 정의
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "parent_id")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "child_id")
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Parent parent;
}
간단한 Parent(1) <-> Child(N)
관계의 도메인을 작성했습니다.
편의상 Getter/Setter 는 생략합니다.
3. BatchSize 정의
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface BatchSize {
int size();
}
@BatchSize
클래스 파일을 보면 위과 같이 나와있습니다.
Type, Method, Field 에 사용할 수 있으며 size
를 설정해야 합니다. (Method 에 설정하는 건 자주 사용하지 않아서 이 포스트에선 제외합니다)
size
는 간단히 말해서 IN
절에 들어갈 요소의 최대 갯수를 의미합니다.
만약 IN
절에 size
보다 더 많은 요소가 들어가야 한다면 여러 개의 IN
쿼리로 나누어 날립니다.
3.1. Type (Class) 에 정의
@BatchSize(size = 100)
@Entity
public class Parent {
...
}
Entity 클래스 위에 붙일 수 있습니다.
만약 다른 엔티티에서 여러 개의 Parent
객체를 프록시로 호출한다면 배치사이즈가 적용되어 IN
쿼리로 조회할 겁니다.
3.2. Field 에 정의
@Entity
public class Parent {
@BatchSize(size = 100)
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
@OneToMany
를 사용하는 Collections 에 붙이면 여러 Parent
객체가 getChildren()
호출할 때 하나의 쿼리로 가져옵니다.
3.3. application.yml 에 정의
spring:
properties:
hibernate:
default_batch_fetch_size: 100
application.yml
에 추가하면 프로젝트 전역으로 배치 사이즈를 적용할 수 있습니다.
4. 호출 테스트
List<Parent> parents = parentRepository.findAll();
// 실제로 사용해야 쿼리가 나가기 때문에 size() 까지 호출해줌
parents.get(0).getChildren().size();
parents.get(1).getChildren().size();
Parent
, Child
데이터가 이미 존재한다고 가정하고 테스트 코드를 작성했습니다.
parents
에서 for
문으로 간단하게 작성해도 되지만 명시적으로 두번 호출해봅니다.
4.1. Before
SELECT * FROM parent
SELECT * FROM child WHERE child.parent_id = 1
SELECT * FROM child WHERE child.parent_id = 2
배치 사이즈를 적용하지 않으면 child
테이블을 조회하기 위해 두 개의 쿼리가 날아갑니다.
만약 parents
의 갯수가 더 많다면 갯수만큼 쿼리가 날아갈겁니다.
4.2. After
SELECT * FROM parent
SELECT * FROM child WHERE child.parent IN (1, 2)
배치 사이즈를 추가하면 여러 쿼리를 하나의 IN
쿼리로 만들어줍니다.
IN
절에 들어가는 요소의 갯수는 설정 가능합니다.
만약 조건 갯수보다 설정한 배치사이즈 크기가 더 작다면 IN
쿼리가 추가로 날아갑니다.
예를 들어 size
를 100 으로 설정했기 때문에 데이터가 250 개라면 1 ~ 100, 101 ~ 200, 201 ~ 250 이렇게 세 번에 나누어서 IN
쿼리를 날립니다.
(사실 완전히 똑같은 사이즈로 분배해서 날리지는 않고 내부적으로 최적화한 사이즈로 나누어서 날립니다)
5. Conclusion
BatchSize 옵션을 사용하면 비슷한 조회 쿼리 데이터들을 한번에 가져올 수 있어 성능적으로 효과를 볼 수 있습니다.