JPA/Querydsl

Querydsl 17 - 스프링 데이터 jpa가 제공하는 Querydsl 기능

킹차니 2022. 1. 31. 16:06

사실 스프링 데이터 jpa가 제공하는 기능들은 제약이 커서 실무에서 사용하기 쉽지 않다. 하여 간단히 소개하고, 왜 부족한지 알아보자.

 

1. 인터페이스 지원 - QuerydslPredicateExecutor

 

아래가 스프링 데이터 jpa QuerydslPredicateExecutor 인터페이스이다.

public interface QuerydslPredicateExecutor<T> {
	Optional<T> findById(Predicate predicate);
    Iterable<T> findAll(Predicate predicate);
    long count(Predicate predicate);
    boolean exists(Predicate predicate);
    // _ more functionality omitted.
}

 

직접 사용해보자.

아래와 같이 QuerydslPredicateExecutor를 implements한다.

import org.springframework.data.querydsl.QuerydslPredicateExecutor;

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom, QuerydslPredicateExecutor<Member> {
    List<Member> findByUsername(String username);
}

이를 implements하면 QuerydslPredicateExecutor 인터페이스에 정의된 메서드를 모두 사용할 수 있다. 특히 메서드의 인자로 Predicate 타입을 직접 넘겨 주는 데, 이는 querydsl 사용시 where조건문에 넣었던 타입이다.

 

 이를 테스트해보자.

@Test
void querydslPredicateExecute() {
    QMember member = QMember.member;
    Iterable<Member> result = memberRepository.findAll(member.age.between(10, 40).and(member.username.eq("member1")));
    for (Member m : result) System.out.println("m = " + m);
}

이를 실행하면 아래와 같은 쿼리가 나간다.

    select
        member1 
    from
        Member member1 
    where
        member1.age between ?1 and ?2 
        and member1.username = ?3

 

하지만 QuerydslPredicateExecutor는 아래와 같은 한계점이 있다.

1. 조인X (묵시적 조인은 가능하지만 left join이 불가능하다.)
2. 클라이언트가 Querydsl에 의존해야 한다. 서비스 클래스가 Querydsl이라는 구현 기술에 의존해야 한다.

3.복잡한 실무환경에서 사용하기에는 한계가 명확하다.

 


2. 리포지토리 지원 - QuerydslRepositorySupport

장점

1. getQuerydsl().applyPagination() 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 가능(! Sort는 오류발생)

2. from()으로 시작 가능(최근에는 QueryFactory를 사용해서 select() 로 시작하는 것이 더 명시적)

3. EntityManager 제공

아래가 스프링 데이터 jpa가 제공하는 QuerydslRepositorySupport이다.

@Repository
public abstract class QuerydslRepositorySupport {

   private final PathBuilder<?> builder;

   private @Nullable EntityManager entityManager;
   private @Nullable Querydsl querydsl;

   public QuerydslRepositorySupport(Class<?> domainClass) {
      Assert.notNull(domainClass, "Domain class must not be null!");
      this.builder = new PathBuilderFactory().create(domainClass);
   }

   @Autowired
   public void setEntityManager(EntityManager entityManager) {
      Assert.notNull(entityManager, "EntityManager must not be null!");
      this.querydsl = new Querydsl(entityManager, builder);
      this.entityManager = entityManager;
   }

   @PostConstruct
   public void validate() {
      Assert.notNull(entityManager, "EntityManager must not be null!");
      Assert.notNull(querydsl, "Querydsl must not be null!");
   }

   @Nullable
   protected EntityManager getEntityManager() {
      return entityManager;
   }

   protected JPQLQuery<Object> from(EntityPath<?>... paths) {
      return getRequiredQuerydsl().createQuery(paths);
   }

   protected <T> JPQLQuery<T> from(EntityPath<T> path) {
      return getRequiredQuerydsl().createQuery(path).select(path);
   }

   protected DeleteClause<JPADeleteClause> delete(EntityPath<?> path) {
      return new JPADeleteClause(getRequiredEntityManager(), path);
   }

   protected UpdateClause<JPAUpdateClause> update(EntityPath<?> path) {
      return new JPAUpdateClause(getRequiredEntityManager(), path);
   }

   @SuppressWarnings("unchecked")
   protected <T> PathBuilder<T> getBuilder() {
      return (PathBuilder<T>) builder;
   }

   @Nullable
   protected Querydsl getQuerydsl() {
      return this.querydsl;
   }

   private Querydsl getRequiredQuerydsl() {
      if (querydsl == null) 
         throw new IllegalStateException("Querydsl is null!");
      return querydsl;
   }

   private EntityManager getRequiredEntityManager() {
      if (entityManager == null) 
         throw new IllegalStateException("EntityManager is null!");
      return entityManager;
   }
}

 

이를 한번 extends하여 사용해보자.

public class MemberRepositoryImpl extends QuerydslRepositorySupport implements MemberRepositoryCustom{

    public MemberRepositoryImpl() {
        super(Member.class); /* 엔티티타입을 super에 넘겨준다.*/
    }
}

 이전에 사용한 MemberRepositoryImpl에서 QuerydslRepository를 extends하였다. 그리고 생성자에 super(엔티티타입)을 해주는 것을 볼 수 있다.

 여기에 page메서드를 작성할 시에 offset과 limit를 생략할 수 있다.

public class MemberRepositoryImpl extends QuerydslRepositorySupport implements MemberRepositoryCustom{

    public MemberRepositoryImpl() {
        super(Member.class); /* 엔티티타입을 super에 넘겨준다.*/
    }

    @Override
    public List<MemberTeamDto> search2(MemberSearchCondition condition) {
        List<MemberTeamDto> result = from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name
                ))
                .fetch();
    }
}

 

하지만 아래와 같은 한계점이 있다.

1. Querydsl 3.x 버전을 대상으로 만듬

2. Querydsl 4.x에 나온 JPAQueryFactory로 시작할 수 없음

3. select로 시작할 수 없음 (from으로 시작해야함) QueryFactory 를 제공하지 않음

4. 스프링 데이터 Sort 기능이 정상 동작하지 않음

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.