본문 바로가기

카테고리 없음

JPA - N + 1 문제, FetchType에 관하여

JPA를 사용할 때 주의해야 할 것들이 크게 세가지 정도 있는 것 같다.

1. N + 1 문제 

2. 연관관계 맵핑

3. 변경감지

 

오라클이나 mysql 환경에서 쿼리를 짜면 실행계획을 확인할 수 있어서 인덱스를 만들지, 쿼리를 어떻게 수정할 지 금방할 수 있다. 근데 JPA를 사용할 때는 성능 최적화 까지는 아니더라도 내가 의도한대로 작동하길 바라려면 알아야 할 것들이 많다. 그렇다고 해서 러닝커브가 엄청 큰 것이나 이해하기 난해한 것도 아니여서 개발하면서 중간중간 저 위의 세가지를 되새기면서 코딩하면 크게 문제 될 것도 없을 것 같다.

 

본론으로 돌아와서,

N+1 문제도 JPA 패러다임 불일치/추상화 문제중 하나이다. 너무 추상화 되어서 사용하면서 중요한 부분을 빼먹는 경우다. 원래 제공되는 스펙대로 사용하면 문제 없다.

 

N + 1 문제란?

하나의 테이블에 쿼리를 날렸는데, 그 테이블에 외래키를 걸고 있는 다른 테이블이 실행되는 것이다.

만약 크 외래키를 걸고 있는 테이블에 외래키를 걸고있는 테이블이 존재하면 1 + N + (N * N) + .... 의 문제가 발생할 수 있다. JPA에서는 이를 해결하기 위해서 엔티티 하나를 가져올 때 다른 엔티티를 바로 가져오는 것이 아니라 프록시로 먼저 가져온 뒤 나중에 실제로 필요하면 그때 다시 가져오는 전략을 취한다. 일명 지연로딩 전략이다. 

 

흔히 드는 예시로,

집사 1명과 그가 키우는 고양이 N마리가 있다면,

나는 집사 1명에 해당하는 실행시간을 예상했지만 1 + N의 조회를 기다려야하는 문제가 발생하는 예시가 있다.

 

 

이런 문제가 발생하는 이유는

1. 엔티티 fetch type자체가 eager로 돼있을 경우 스프링 부트가 엔티티를 즉시 로딩하면서 문제가 발생한다.

 

2. 지연 로딩으로 필요한 데이터만 가져와도 결국 고양이와 관련된 데이터를 보기 위해서는 jpa도 jpql를 사용하기에 하위 엔티티를 다시 조회하면서 쿼리 문이 여러방 나가게 된다.

 

이를 해결하기 위해서는

1. 모든 fetchtype을 lazy로 바꿔줘야한다. XXXtoMany의 경우 디폴트가 lazy이기때문에 걱정할 필요가 없지만, XXXtoONE인 경우 디폴트가 eager다 찾아서 다 바꿔주자,,

 

2. fetch join을 사용하면 JPA가 쿼리를 하나로 만들어서 보내준다.