[Spring] 패치 전략(Fetch Type)

Sun choi·2024년 8월 28일

NEW 지식

목록 보기
20/34

JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지, 필요할 때 가져올지 정할 수 있다.
즉, 가져오는 방법을 정하게되는데 JPA에서는 Fetch Type이라 부른다.

Fetch Type의 종류에는 2가지가 있는데 하나는 LAZY, 다른 하나는 EAGER 이다.

Fetch Eager

즉시로딩
JPA의 엔티티에서 연관 관계를 맺은 정보를 바로 가져온다.
예를 들어, 노션에서 토글들 다 열어둔 것(쿼리 한번)

Fetch Lazy

지연로딩
JPA의 엔티티에서 연관 관계를 맺은 정보를 사용할 때 가져온다.
예를 들어, 필요한 토글만 열어서 가져오는 것(쿼리 N번)

연관관계 애너테이션들엔 Fetch Type에 default 값이 있는데
애너테이션 이름에서 뒤쪽에 Many가 붙어있으면 설정된 해당 필드가 Java 컬렉션 타입일 것이다. 즉, 해당 Entity의 정보가 여러 개 들어있을 수 있다는 것을 의미한다. 따라서 효율적으로 정보를 조회하기 위해 지연 로딩 이 default로 설정되어있다.
반대로 이름 뒤쪽이 One일 경우 해당 Entity 정보가 한 개만 들어오기 때문에 즉시 정보를 가져와도 무리가 없어 즉시 로딩이 default로 설정되어있다.
(이런 부분들은 default옵션을 사용한다고 하더라도 명시해주는 것이 협업하는 다른 개발자가 보기에도 좋다)

지연 로딩도 마찬가지로 영속성 컨텍스트의 기능(1차 캐시, 쓰기 지연 저장소, 변경 감지) 중 하나이다.
따라서 지연 로딩된 Entity의 정보를 조회하려고 할 때는 반드시 영속성 컨텍스트가 존재해야 한다. ‘영속성 컨텍스트가 존재해야한다’라는 의미는 결국 ‘트랜잭션이 적용되어있어야 한다’라는 의미와 동일! 메서드에 @Transactional 애너테이션!

실질적으로 지연로딩만 사용하는 이유

아래와 같은 이유로 가급적이면 지연 로딩만 사용하는 것이 권장된다.

즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
@ManyToOne, @OneToOne은 기본이 즉시 로딩이기 때문에, LAZY로 따로 설정해야 한다.

한번에 모든 데이터를 다 불러오는 즉시로딩을 사용할 경우 수많은 개발자가 제어할 수 없는 쿼리가 실행되는 불상사가 발생한다.
예를 들어, 즉시 로딩에서는 Member와 연관된 Team이 1개여서 Team을 조회하는 쿼리가 1개 나갔지만, 만약 Member를 조회하는 JPQL을 날렸는데 연관된 Team이 1000개라면? Member를 조회하는 쿼리를 하나 날렸을 뿐인데 Team을 조회하는 SQL 쿼리 1000개가 추가로 나가게 된다.

프록시(Proxy)

실질적인 데이터 있기 전에 있는 것처럼 보여지는 깡통 객체를 프록시 객체라고 한다. 빈 깡통! 있는척하는 애!실제로 있는 것처럼 나타나지만 눌러보면 다 Null 값으로 나온다.(가짜 객체)

예를 들어, 지연 로딩의 경우에는 Member 객체를 얻기위한 쿼리만 DB로 날린다. 그리고 가짜 프록시 Team 객체를 생성해둔다. 이후, 실제로 Team 객체가 사용되는 시점에 DB에서 가져와, 이 프록시 객체를 초기화시킨다.

지연로딩(Lazy) 때문에 생긴 대표적인 N+1 문제

조회 시 1개의 쿼리를 생각하고 설계를 했으나 나오지 않아도 되는 조회의 쿼리가 N개가 더 발생하는 문제.

객체에 대해서 조회한다고 해도 다양한 연관관계들의 매핑에 의해서 관계가 맺어진 다른 객체가 함께 조회되는 경우에 N+1이 발생하게 된다. 성능적으로 엄청 큰 문제이다.

해결방법!
1. 엔티티에서 연관관계 설정을 FetchType.EAGER로 변경
2. JPQL로 직접 패치 조인 쿼리문 작성
3. 배치 사이즈 설정
4. @EntityGraph 어노테이션 사용

profile
풀스택 개발자의 공부기록 📖

0개의 댓글