[QueryDSL] 스프링 데이터 JPA가 제공하는 Querydsl 기능

윤경·2021년 12월 13일
0

QueryDSL

목록 보기
11/11
post-thumbnail

지금 소개할 기능들은 제약이 크기 때문에 복잡한 실무 환경에는 부적합하다.
그래도 스프링 데이터에서 제공하는 기능이니 알고 지나가자.

[1] 인터페이스 지원 - QuerydslPredicateExecutor

QuerydslPredicateExecutor이란 인터페이스를 지원하고 있다.

🔗 [공식 URL](https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/ #core.extensions.querydsl)

리포지토리에 적용하기

✔️ MemberRepository.java

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom, QuerydslPredicateExecutor<Member> {
...

테스트

        QMember member = QMember.member;
        Iterable<Member> result = memberRepository.findAll(
                member.age.between(10, 40)
                        .and(member.username.eq("member1"))
        );

(나머지 코드는 위의 코드 멤버 추가하는 코드)

한계점

  • 조인이 불가능 (🔗 묵시적 조인은 가능하지만 left join이 불가능하다.)
  • 클라이언트가 Querydsl에 의존해야 한다.
    서비스 클래스가 Querydsl이라는 구현 기술에 의존해야 한다. (이때, querydsl을 다른 걸로 바꿔야 하는 상황이 생기면 굉장히 난감해진다.)

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

⚠️ QuerydslPredicateExecutorPageable, Sort를 모두 지원하고 정상 동작한다.
(하지만 위의 한계점 때문에 실무에서는 개인적으로 권장하지 않는 방법)


[2] Querydsl Web 지원

🔗 [공식 URL](https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/ #core.web.type-safe)

코드가 한줄로 엄청 간결해질 수는 있지만 한계점 때문에 실무에서는 정작 사용할 순 없는,,

한계점

  • 단순한 조건만 가능
  • 조건을 커스텀하는 기능이 복잡하고 명시적이지 않음
  • 컨토롤러가 Querydsl에 의존 (구현 기술을 바꾸게 되면 큰일)
  • 복잡한 실무 환경에서 사용하기에는 한계가 명확

[3] 리포지토리 지원 - QuerydslRepositorySupport

장점

  • getQuerydsl().applyPagination() 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 가능(단!! Sort는 오류 발생!!)
  • from()으로 시작 가능 (최근에는 QueryFactory를 사용해 select()로 시작하는 것이 더 명시적)
  • EntityManager 제공

단점

  • Querydsl 3.x 버전을 대상으로 만듦
  • Querydsl 4.x에 나온 JPAQueryFactory로 시작할 수 없음
    즉, select로 시작할 수 없음(from으로 시작해야 한다) ➡️ 한 눈에 잘 안 들어옴
  • QueryFactory를 제공하지 않음
  • 스프링 데이터 Sort 기능이 정상 동작하지 않음 → 조심

[4] Querydsl 지원 클래스 직접 만들기

: 스프링 데이터가 제공하는 QuerydslRepositorySupport가 지닌 한계를 극복하기 위해 직접 Querydsl 지원 클래스를 만들기

장점

  • 스프링 데이터가 제공하는 페이징을 편리하게 변환
  • 페이징과 카운트 쿼리 분리 가능
  • 스프링 데이터 Sort 지원 (이전엔 Sort가 안된다고 했었음)
  • select(), selectFrom()으로 시작 가능
  • EntityManager, QueryFactory 제공

코드

✔️ Querydsl4RepositorySupport.java

/**
 * Querydsl 4.x 버전에 맞춘 Querydsl 지원 라이브러리
 *
 * @author Younghan Kim
 * @see
org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
 */

@Repository
public abstract class Querydsl4RepositorySupport {

    private final Class domainClass;
    private Querydsl querydsl;
    private EntityManager entityManager;
    private JPAQueryFactory queryFactory;

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

    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        Assert.notNull(entityManager, "EntityManager must not be null!");

        JpaEntityInformation entityInformation =
                JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);

        SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
        EntityPath path = resolver.createPath(entityInformation.getJavaType());
        this.entityManager = entityManager;
        this.querydsl = new Querydsl(entityManager, new
                PathBuilder<>(path.getType(), path.getMetadata()));
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

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

    protected JPAQueryFactory getQueryFactory() {
        return queryFactory;
    }

    protected Querydsl getQuerydsl() {
        return querydsl;
    }

    protected EntityManager getEntityManager() {
        return entityManager;
    }

    protected <T> JPAQuery<T> select(Expression<T> expr) {
        return getQueryFactory().select(expr);
    }

    protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return getQueryFactory().selectFrom(from);
    }

    protected <T> Page<T> applyPagination(Pageable pageable,
                                          Function<JPAQueryFactory, JPAQuery> contentQuery) {

        JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());

        List<T> content = getQuerydsl().applyPagination(pageable,
                jpaQuery).fetch();

        return PageableExecutionUtils.getPage(content, pageable,
                jpaQuery::fetchCount);
    }

    protected <T> Page<T> applyPagination(Pageable pageable,
                                          Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory,
            JPAQuery> countQuery) {

        JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());

        List<T> content = getQuerydsl().applyPagination(pageable,
                jpaContentQuery).fetch();

        JPAQuery countResult = countQuery.apply(getQueryFactory());

        return PageableExecutionUtils.getPage(content, pageable,
                countResult::fetchCount);
    }
}

✔️ MemberTestRepository.java

@Repository
public class MemberTestRepository extends Querydsl4RepositorySupport {

    public MemberTestRepository(Class<?> domainClass) {
        super(Member.class);
    }

    public List<Member> basicSelect() {
        return select(member)
                .from(member)
                .fetch();
    }

    public List<Member> basicSelectFrom() {
        return selectFrom(member)
                .fetch();
    }

    public Page<Member> searchPageByApplyPage(MemberSearchCondition condition, Pageable pageable) {
        JPAQuery<Member> query = selectFrom(member)
                .leftJoin(member.team, team)
                .where(usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                );

        List<Member> content = getQuerydsl().applyPagination(pageable, query)
                .fetch();

        return PageableExecutionUtils.getPage(content, pageable, query::fetchCount);    // query::fetchCount -> totalCount
    }

    public Page<Member> applyPagination(MemberSearchCondition condition, Pageable pageable) {

        return applyPagination(pageable, query ->
                query.selectFrom(member)
                        .leftJoin(member.team, team)
                        .where(usernameEq(condition.getUsername()),
                                teamNameEq(condition.getTeamName()),
                                ageGoe(condition.getAgeGoe()),
                                ageLoe(condition.getAgeLoe())
                        )
        );
    }

    public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) {

        return applyPagination(pageable, contentQuery -> contentQuery
                .selectFrom(member)
                .leftJoin(member.team, team)
                .where(usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())

                ), countQuery -> countQuery
                .select(member.id)
                .from(member)
                .leftJoin(member.team, team)
                .where(usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
        );
    }

    private BooleanExpression usernameEq(String username) {
        return hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }
}

profile
개발 바보 이사 중

0개의 댓글