JPA

spring boot 3 Query Dsl적용 (네이티브 쿼리 작성으로 N+1문제 해결)

Lahezy 2023. 3. 31.
728x90

지금 까지 모든 포스트를 확인하면 위처럼 포스트의 개수만큼 쿼리를 조회하였다.

(Fatch type을 LAZY로 하여서 포스트에 걸려있는 좋아요의 수를 나중에 불러와서 발생하는 문제였다)

계속 고쳐야지 하다가 이번에 고쳐버렸다.

 

JPQL로 FetchType.LAZY와 Fetch join 하는 방법고 batch 사이즈를 조정하는 방법등 다양한 방법이 있는것 같았지만 나는 query dsl을 적용하여 해결해보고 싶어 진행하였다. 

 

그래서 현재는 아래처럼 한 번의 쿼리로 해결되는 것으로 보인다.

어째서 인지는 모르겠는데 테스트 코드에서는 N+1문제가 발생하지 않았다..? 그래서 한 번 더 확인해 보는 걸로 

Query dsl을 스프링 부트 3.0 버전에 적용하는 방법은 블로그와 강의를 참고해서 세팅하였다.

 

[세팅 참고 블로그]

https://jessyt.tistory.com/14

https://velog.io/@juhyeon1114/Spring-QueryDsl-gradle-%EC%84%A4%EC%A0%95-Spring-boot-3.0-%EC%9D%B4%EC%83%81

 

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
    // id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"//인텔리제이 탭에서 그래들 관련 명령어를 쉽게 볼 수 있게 해준다(선택) 
    // 해당 플러그인 추가로 동일한 Q파일 생성 문제가 발생한다고 한다 추가하지 말자! 
}

dependencies {
    // querydsl 추가
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

//querydsl 추가 시작 (선택 추가)

def querydslDir = "$buildDir/generated/querydsl"
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가 끝

DataBaseConfiguration

@Configuration
public class DataBaseConfiguration {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

해당 파일은 위의 블로그에서 참고하여 그대로 넣었다.

위의 사진처럼 파일을 구성하고 코드는 아래와 같다. 

QuerydslRepositorySupport를 사용하는 경우 아래와 같이 사용할 수 있다.

 

💡참고: QuerydslRepositorySupport는 Querydsl의 사용을 도와주는 유틸리티 클래스입니다. Repository인터페이스를 확장하여 Querydsl을 사용하여 더욱 복잡한 동적쿼리를 작성할 수 있습니다.

 

JPAQueryFactory는 Querydsl을 사용하여 JPA를 통해 데이터베이스와 상호작용하기 위한 도구로 EntityManager를 기반으로 하여 동작하는것으로 위에서 생성된 bean이 주입됩니다. 

 

@Repository
@Slf4j
public class PostRepositoryImpl extends QuerydslRepositorySupport {
    private final JPAQueryFactory jpaQueryFactory;

    public PostRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
        super(Post.class);
        this.jpaQueryFactory = jpaQueryFactory;
    }
}

실제 query dsl을 이용한 조회

 

build -> clean
other -> compileJava

build 실행 (run)

package laheezy.community.repository;

import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;
import laheezy.community.domain.Post;
import laheezy.community.dto.post.PostResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.stream.Collectors;

import static laheezy.community.domain.QPost.post;
import static laheezy.community.domain.QPostHeart.postHeart;

@Repository
@Slf4j
public class PostRepositoryImpl extends QuerydslRepositorySupport {
    private final JPAQueryFactory jpaQueryFactory;

    public PostRepositoryImpl( JPAQueryFactory jpaQueryFactory) {
        super(Post.class);
        this.jpaQueryFactory = jpaQueryFactory;
    }

    public List<PostResponseDto> findAllPostWithHeartCnt() {
        log.info("findAllPost Query");
        List<Tuple> results = jpaQueryFactory
                .select(post, postHeart.id.count())
                .from(post)
                .leftJoin(post.postHearts, postHeart)
                .groupBy(post)
                .fetch();

        return results.stream().map(tuple -> {
            Post p = tuple.get(post);
            Long heartCount = tuple.get(postHeart.id.count());
            PostResponseDto dto = new PostResponseDto(p.getId(), p.getMember().getNickname(), p.getTitle(), p.getText(), p.isOpen(), p.getView(), p.getWriteDate(), heartCount);
            return dto;
        }).collect(Collectors.toList());
    }
}

위의 코드는 조회 코드 예시이다. 현재는 아직 패키지 구성과 실제 사용방법이 미숙하여 임시로 세팅해 두었다. 

728x90

댓글