Querydsl 설정하기

트곰·2022년 4월 10일
0

newproject

목록 보기
8/13

오랜만에 블로그를 작성하는 것 같다.
querydsl 강의를 듣고, 쿼리를 짜고 이제 얼추 로직도 구현이 되었다.
JpaRepository로 기본적인 find메소드나 자동완성으로 select 기능을 사용하다가, 디테일한 select가 필요해서 작업 중간에 querydsl을 학습하고 구현하기 시작했다.

하나의 엔티티에 레포지토리를 2개두고, querydsl을 사용하는 클래스에서는 메소드를 구현해서 사용을 했다.
querydsl 관련한 config 클래스도 만들어서 테스트를 한 후, 정상적으로 작동하는 것을 확인했지만 자꾸 nullpointerException이 뜬다..!!!!
patient를 못찾아서 오류가 생긴거라고 생각하고, 정보가 제대로 전달되는지 확인했는데 거기가 문제가 아니였다.
문제가 뭔지도 찾는데 한참 걸렸는데ㅠㅠ 문제는 JPAQueryFactory!!
그니까 레포지토리 자체를 찾지 못하는 문제였다..

우선 레포지토리는 당연하겠지만, 한 엔티티에 하나씩 사용이 가능하다. 나는 두 개의 레포지토리에 모두 어노테이션을 붙여서 못 읽은 것이라고 생각하고 JpaRepository를 상속받은 기본 인터페이스는 주석 처리했지만, 레포지토리의 메소드를 읽어 오지 못했다.

querydsl를 설정하는 방법은 총 3가지가 있다고 하는데, 나는 그 중에 가장 간단한 방법인 config 파일을 만들고, 레포지토리 내부에서 JPAQueryFactory를 선언해서 사용하는 방법으로 코드를 작성했다.
근데 그 부분에서 문제가 있었던 것인지 레포에 아예 접근을 하지 못했다.

그래서, RepositoySupport를 사용하는 방법과
RepositoryCustom, RepositoryImpl을 사용하는 방법 두 가지 모두 테스트해보았고 정상 작동하는 것을 확인했다.

그러면 내가 만들어서 동작한 코드에 대해서 살펴보겠다.


우선 공통적으로는 JPAQueryFactory의 빈 등록이 필요하다.
두 가지 모두 config 클래스를 만들어 준다.
설정을 변경하면서, 클래스 자체를 주석처리 해봤는데, 빈을 찾을 수 없다고 하며 컴파일 과정에서 실행이 실패되었다.


import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Configuration
public class QuerydslConfiguration {

    @PersistenceContext
    private EntityManager entityManager;

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

1. QuerydslRepositorySupport

장점

  • 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 가능(단, Sort는 오류 발생)
    : getQuerydsl().applyPagination()
  • from()으로 시작 가능
  • EntityManager 제공

한계

  • Querydsl 3.x 버전을 대상으로 만들었다.
  • Querydsl 4.x에서 나온 JPAQueryFactory로 시작할 수 없어서 select로 시작할 수 없음
  • QueryFactory를 제공하지 않음
  • 스프링 데이터 Sort 기능이 정상 동작하지 않음
@Repository
public class IncomeQueryRepositorySupport extends QuerydslRepositorySupport {

    private final JPAQueryFactory queryFactory;

    public IncomeQueryRepositorySupport(JPAQueryFactory queryFactory) {
        super(Income.class);
        this.queryFactory = queryFactory;
    }

   
    public Optional<Income> findLastVisitIncomeByPatient (Patient patient) {
        Income income = queryFactory.selectFrom(QIncome.income)
                .where(QIncome.income.patient.eq(patient))
                .orderBy(QIncome.income.date.desc())
                .fetchFirst();
        return Optional.ofNullable(income);
    }
    
    ...

}

이동욱님 블로그를 참고해서 만들었고, 위에 코드는 내가 만든 support 클래스를 그대로 가지고 왔다.
QuerydslRepositorySupport를 상속받은 클래스를 레포지토리로 만들고,
생성자에서 JPAQueryFactory를 주입받아서 사용했다.
위에 한계에서는 JPAQueryFactory로 시작할 수 없어서 select문으로 사용이 불가능하다고 했는데, select로 시작해도 정상적으로 코드가 잘 돌아갔다.

이렇게 사용하면, JpaRepository를 상속받은 기본적인 인터페이스 레포지토리가 하나 더 필요하다.
save, delete 등은 해당 레포지토리로 사용하고, querydsl을 사용한 코드는 support 레포지토리에서 호출해서 사용하는 방식으로 쓰면 된다.

2. Custom, Impl, Repository

위의 방식을 사용할 경우, 2개의 레포지토리를 의존성으로 받아야 하는 단점이 있어서
Custom Repository를 JpaRepository 상속 클래스에서 사용할 수 있도록 만들어봅니다.

(1)custom interface 만들기

public interface IncomeRepositoryCustom {
 
    public Long countVisitByPatient(Patient patient);

}

사용할 추상 메소드를 만들어준다.

(2)Impl class 만들기

@RequiredArgsConstructor
public class IncomeRepositoryImpl implements IncomeRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    public Long countVisitByPatient(Patient patient) {
        return queryFactory.selectFrom(QIncome.income)
                .where(QIncome.income.patient.eq(patient))
                .fetchCount();
    }

}

위에서 만든 인터페이스를 구현한 구현체 클래스를 만들어주고, 추상 메소드를 override한다.
JPAQueryFactory를 주입받는다.

(3)Repository interface 만들기

@Repository
public interface IncomeRepository extends JpaRepository<Income, Long>, IncomeRepositoryCustom {

}

JpaRepository, Custom 인터페이스를 상속하는 인터페이스를 만들고 @Repository 어노테이션을 붙여준다.
만약 페이징 처리가 필요하다면, QuerydslSupport도 상속하는 것 같다.


처음에는 파일을 많이 만들고 싶지 않아서 JPAQueryFactory를 필드 주입받아서 레포지토리 클래스를 만드는 방식으로 사용했는데, 원하는대로 작동이 하지 않아서 나머지 두 가지 방식을 사용해보았다.

첫 번째 방법도 결국 레포지토리 2개에 의존받는 것은 동일했고, 두 번째 방법은 파일(클래스/인터페이스)가 더 많이 생기는 것은 있지만 실질적으로 의존성으로 받는 레포지토리는 1개라는 점에 차이가 있었다.

가장 간단한 방법은 실패했지만, 정상적으로 작동한다면 해당 내용도 정리해서 추가해야지!!

profile
개발자가 되기 위해서 공부중입니다 :ㅡ)

0개의 댓글