- 특정 객체를 조회하는 상황에서 그 객체가 가지고 있는 연관관계가 설정된 객체에 접근할 때 발생하는 성능 문제
- 즉, 특정 객체를 조회하는 1번의 쿼리 이후, 해당 객체와 연관된 데이터를 가져오기 위해 객체의 개수(N)만큼 추가 쿼리가 발생하는 현상
- 예를 들어, 게시글과 댓글이
1:N관계일 때, 게시글 목록을 한 번에 조회(1번 쿼리)한 뒤, 각 게시글의 댓글을 가져오기 위해 N번의 쿼리가 추가로 실행되는 상황입니다. 이처럼 단일 쿼리로 해결할 수 있는 작업이 불필요하게 여러 쿼리로 분산되면서 성능 저하가 발생함.
-- 게시글 전체 조회 (1번 쿼리)
SELECT * FROM post;
-- 첫 번째 게시글의 댓글
SELECT * FROM comment WHERE post_id = 1;
-- 두 번째 게시글의 댓글
SELECT * FROM comment WHERE post_id = 2;
-- 세 번째 게시글의 댓글
SELECT * FROM comment WHERE post_id = 3;- 객체지향 언어와 RDB 간의 패러다임 불일치를 해결하기 위해 나온 기술로 객체와 RDB 테이블을 매핑해주는 기술이다.
- 구조와 개념이 달라 불일치 문제가 발생함. 객체지향적으로 RDB를 다룰 수 있게 해줌
- 뭐 객체지향은 상속, 참조 등이 있지만 RDB는 없음
- 자바에서 사용하는 ORM 기술 표준.
- JPA 에서 SQL을 추상화하여 사용되는 객체지향 쿼리 언어로서, 테이블이 아닌
엔티티를 대상으로 쿼리를 작성한다. - JPQL은 JPA의 구현체인 Hibernate가 SQL로 변한하고 JDBC를 통해서 실제 DB에 접근한다.
- JPQL은 기본적으로 루트 엔티티만 조회하고 연관관계와 패치 전략 모두 무시한다. 따라서 연관관계에 접근할 떄 추가적인 쿼리가 발생함
-
- JPQL에서 성능 최적화를 위해 제공하는 기능으로 join fetch로 한 번에 모두 조회한다.
- 패치 조인을 사용하면 엔티티의 fetch 전략(지연/즉시 로딩) 설정과 상관없이, 쿼리에서 지정한 연관 엔티티를 한 번에 모두 즉시 로딩
일반 JOIN(fetch 전략이 LAZY일 떄)- JPQL에서 단순히 join을 사용하는 경우 또는 LAZY일 때 실제로는 루트 엔티티만 영속성 컨텍스트에 저장되고, 연관 엔티티는 프록시 객체로 남아 있는다. 따라서 연관 엔티티에 접근할 때마다 추가 쿼리가 발생
fetch Join- join fetch 구문을 사용하면, 루트 엔티티와 연관 엔티티를 모두 실제 객체로 한 번에 영속성 컨텍스트에 저장합니다. 이로써 추가 쿼리 없이 연관 데이터를 바로 사용
- 즉시 로딩 전략도 한번에 다 가져오긴 하지만 쿼리에
join fetch를 명시 하지 않음으로 N+1 발생 - 기본적으로 inner Join
- OneToOne, ManyToOne ("ToOne") 관계에 대해선 몇개든 사용 가능하다.
- ManyToMany, OneToMany ("ToMany") 는 단 1개만 사용 가능하다.
- JPQL 에서 지원 X
-
- Spring Data JPA에서 제공하는 기능으로, JPQL로 fetch join을 직접 작성하지 않고 @EntityGraph을 사용하여 편리하게 사용할 수 있도록 해준다.
- @EntityGraph는 left outer join 만 지원함.
- 런타임에 fetchType.LAZY를 fetchType.EAGER로 전환하여 데이터를 가져온다
-
- IN 쿼리로 묶어서 쿼리 수를 줄임
- JPA에서 Batch Size를 설정하는 두 가지 방법이 있다.
- @BatchSize 어노테이션을 사용
- application.properties 또는 application.yml 파일에서 전역 설정으로 batch size를 지정(필자는 해당 방법 선택)
- 모든 LAZY 연관관계에 적용됨.
- Batch Size를 너무 크게 설정하면, 한 번에 로드되는 데이터 양이 많아져서 메모리 사용량이 증가할 수 있다.
-
- querydsl을 이용할 수도 있음.
- 카테시안 곱 : 두 테이블 사이에 유효 join 조건을 적지 않았을 때 해당 테이블에 대한 모든 데이터를 전부 결합하여 테이블에 존재하는 행 갯수를 곱한만큼의 결과 값이 반환되는 것
- 해결 방안 (@Query에 적용하면 됨.)
- 일대다 필드의 타입을
Set으로 선언 distinct를 사용하여 중복 제거
- 일대다 필드의 타입을