이게 API 1회 조회 시 날아가는 쿼리문의 양이다.. Reservation 을 조회 시 마다 해당 테이블에 Counselor 가 같이 있는데 해당 테이블을 예약 갯수 만큼의 쿼리문이 추가적으로 날라가는 듯 하다.
이렇게 계속 많은 양의 쿼리문은 성능을 저하시키고 서버를 개발한다면 꼭 최적화 해야하는 부분이라고 생각한다.
그렇다면 해결 방법이 뭐가 있을까?
일단 나는 QueryDsl 을 쓰지 않겠다 하면 해당 방법이 존재하겠다. @EnittyGraph 가 뭔지 나는 몰랐었는데, 해당 어노테이션은 N+1 문제 해결 및 성능 최적화에 간편하게 도움을 줄 수 있게 Fetch Join 을 적용시켜주는 어노테이션이다. 필요한 부분에 아래와 같이 적용하면 된다.
@EntityGraph(attributePaths = {"counselor"})
@Query("SELECT r FROM Reservation r LEFT JOIN r.counselor c WHERE r.account.email = :accountEmail ORDER BY r.createdAt DESC")
List<Reservation> findAllByAccount_EmailOrderByCreatedAtDesc(String accountEmail);
주의 사항
@EntityGraph 는 left outer join 만 지원한다.
다른 방식의 경우 JPQL 을 직접 작성해 fetch join 을 사용해야만 한다.
아니면 QueryDsl 을 사용하여 FetchJoin 을 진행하면 되는데, Query Dsl 의 경우 다음과 같이 작성하면 된다.
QueryDslConfig.java
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
build.gradle
// Querydsl-JPA
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
CustomRepositoryImpl.java
@RequiredArgsConstructor
public class CustomReservationRepositoryImpl implements CustomReservationRepository{
private final JPAQueryFactory queryFactory;
@Override
public List<Reservation> findAllByAccountEmail(String email){
return queryFactory.select(reservation)
.from(reservation)
.leftJoin(reservation.account, accountDetail)
.leftJoin(reservation.counselor, counselor)
.where(accountDetail.email.eq(email))
.orderBy(reservation.createdAt.desc())
.fetchJoin().fetch();
}
}
이렇게 작성하면 QueryDsl 을 이용하여 fetch join을 사용할 수 있다.
이와 같이 매 조회 시 마다 쿼리문이 날라가던 문제가 해결된 것을 볼 수 있다.
최적화 전 API 성능
최적화 후 API 성능
약 39.3%의 성능 개선을 확인할 수 있습니다.