[JPA] default_batch_fetch_size 동작
OneToMany 관계에서 N+1을 해결하기 위해 default_batch_fetch_size를 처음 접하게 되었습니다.
정리 글: https://lback.tistory.com/15
N+1 문제에 사용되는 원리는 위 글에 정리해두었지만, default_batch_fetch_size 자체의 동작에 대해 조금 더 살펴보겠습니다.
기본적인 동작 역시 위 글에 정리해두었지만,
의문이 들었던 점은 2가지 부분입니다.
1. default_batch_fetch_size가 적용되는 경우
먼저 default_batch_fetch_size가 적용되는 경우에 대해 명확히 알고 싶었습니다.
이에 대해서 찾아본 결과, 다음과 같은 설명을 찾을 수 있었습니다.
'N+1 문제를 해결하기 위해, 지연로딩 시 미리 batch 단위로 fetch 한다'는 설명을 접할 수 있었습니다.
여기서 조금 애매했던 것이, 'N+1 문제가 발생할 때만 적용되는지, 아니면 모든 지연로딩에 적용되는지' 의문이 들었습니다.
물론 JPA가 N+1 문제가 발생하고 있다는 것을 판단하고 처리한다는 것은 들어본 적이 없지만, 모든 지연로딩에 적용되는 것임을 확실히 하고 싶었습니다.
2. in 절에 들어갈 값의 조건
다음으로는 in 절에 들어갈 값 조건입니다. in에 들어갈 값이 어떻게 결정되는 것인지 명확하게 하고 싶었습니다.
이에 관해서도 다음과 같은 설명을 찾을 수 있었습니다.
The primary key values to batch fetch are chosen from among the identifiers of unfetched entity proxies or collection roles associated with the session.
Hibernate의 설명인데, 동일한 동작을 하는 @BatchSize에 대한 설명입니다.
동작과 위 설명을 통해 in 절에 들어갈 조건은 '영속성 컨텍스트에서 관리하는 엔티티 중 아직 지연로딩이 되지 않은 연관 엔티티'라고 이해했습니다.
위 두가지 동작이 맞는지 확인하기 위해 간단한 테스트 코드를 작성해봤습니다.
Member -> Locker는 1:1 단방향 관계입니다.
@Test
public void 배치_테스트(){
// 데이터 추가
// 회원3명, 락커 3개
Locker locker1 = new Locker();
locker1.setNumber(10L);
Locker locker2 = new Locker();
locker2.setNumber(20L);
Locker locker3 = new Locker();
locker3.setNumber(30L);
Member member1 = new Member();
member1.setName("회원1");
member1.setLocker(locker1);
Member member2 = new Member();
member2.setName("회원2");
member2.setLocker(locker2);
Member member3 = new Member();
member3.setName("회원3");
member3.setLocker(locker3);
// (Locker 는 cascade 옵션을 통해 저장)
memberRepository.save(member1);
memberRepository.save(member2);
memberRepository.save(member2);
// 플러시 및 영속성 컨텍스트 초기화
em.flush();
em.clear();
Member findMember1 = memberRepository.findById(1L);
Member findMember2 = memberRepository.findById(2L);
System.out.println(findMember1.getLocker().getNumber());
}
(1) 회원 3명과 락커 3개를 저장
(2) 영속성 컨텍스트 초기화
(3) 회원 1과 회원2만 조회한 후, 회원 1의 락커만 조회
이 코드를 실행했을 때, 이해한게 맞다면,
(1) batch 단위로 fetch 가 발생 (in)
(2) in 절에는 현재 영속성 컨텍스트에서 관리하는 회원 1과 회원 2의 id만 포함 (회원 3의 id 제외)
위와 같이 동작해야 합니다.
테스트를 실행하고 쿼리를 확인해보겠습니다.
in 절이 포함된 것을 통해 batch fetch 가 이 경우에도 적용되는 것을 확인할 수 있고, 또 회원을 3명 추가했음에도 현재 2명의 회원만 조회했기 때문에 in 의 크기가 2임을 확인할 수 있습니다.
결론
- default_batch_fetch_size 는 ~ToOne을 포함한 모든 지연 로딩에 적용된다.
- in 절에 들어갈 조건은 현재 영속성 컨텍스트에서 관리하는 엔티티 중, 아직 fetch 되지 않은 엔티티를 기준으로 한다.