스프링/QueryDsl

[QueryDsl] QuerydslRepositorySupport 개선하여 더 쉽고 간결하게 Repository에서 Querydsl 사용하기 (Querydsl4RepositorySupport)

nomoreFt 2022. 3. 23. 15:14

기존의 QueryDsl을 편하게 사용하기 위해 구현된 QuerydslRepositorySupport를, 직접 구현하여 더 강력한 기능을 가진 Querydsl4RepositorySupport를 제작하자.

QuerydslRepositorySupport 기본 abstract 클래스란?

@Repository 
public abstract class Querydsl4RepositorySupport
  • Impl 객체들에 QueryDsl 사용을 서포팅 해주는 추상클래스이다.

QuerydslRepositorySupport 기본 적용법

public class MemberTestRepository extends QuerydslRepositorySupport {
    public MemberTestRepository() {
        super(Member.class);
    }
}
  • 구현체에 extends QuerydslRepositorySupport 추가
  • super(알맞은.class) 해주기

QuerydslRepositorySupport 개선하여 추상 클래스 직접 만들기

  • 기존 QuerydslRepositorySupport -> custom한 abstract class 개선점
    • Paging을 편리하게 변환 (offset,limit 체인을 제거)
    • Paging, count 쿼리 분리 (람다로 그냥 연속적으로 chain으로 받게 함)
    • 스프링 데이터 Sort 사용가능 (기존에 안되는거)
    • from() 시작문을 -> select(), selectFrom() 시작문으로 변경
    • QueryFactory는 빠져서 추가로 의존성 주입 받던거 기본 지원으로 수정

Querydsl4RepositorySupport 분석

전체 코드

@Repository
public abstract class Querydsl4RepositorySupport {
    private final Class domainClass;
    private Querydsl querydsl;
    private EntityManager entityManager;
    private JPAQueryFactory queryFactory;


//1. 도메인 class 받기 (ex)Member.class)
    public Querydsl4RepositorySupport(Class<?> domainClass) {
        Assert.notNull(domainClass, "Domain class must not be null!");
        this.domainClass = domainClass;
    }


//2. EntityManager 주입받기
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        Assert.notNull(entityManager, "EntityManager must not be null!");
        //2-1. Path를 잡아야 Sort 명령시에 오류가 안난다.
        JpaEntityInformation entityInformation =
                JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
        SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
        EntityPath path = resolver.createPath(entityInformation.getJavaType());
        //2-2. entityManager 주입 받은 것으로 querydsl, queryFactory 생성
        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!");
    }


//3. getter로 가져다 쓸 순 있지만, 이후에 그냥 queryFactory를 호출된 형태의 method를 가져다 쓸 수 있게 해준다.    
    protected JPAQueryFactory getQueryFactory() {
        return queryFactory;
    }
    protected Querydsl getQuerydsl() {
        return querydsl;
    }
    protected EntityManager getEntityManager() {
        return entityManager;
    }

사용법과 코드 분석

  • 기존 from() -> select(),selectFrom() 변경, + queryFactory 생략
    • queryFactory.select() -> select()로 간소화 적용 모습

사용 모습

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

코드 분석

이미 내부에서 queryFactory를 불러서 select(표현식) 받아 처리하는 모습

protected <T> JPAQuery<T> select(Expression<T> expr) {
        return getQueryFactory().select(expr);
    }
    protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return getQueryFactory().selectFrom(from);
    }
  • Paging을 편리하게 변환 (offset,limit 체인을 제거)
    • Paging, count 쿼리 분리 (람다로 그냥 연속적으로 chain으로 받게 함)

사용 모습

     // count, content 분리
public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) {
        return applyPagination(pageable, countQuery ->countQuery
            .selectFrom(member)
            .leftJoin(member.team,team)
            .where(
                usernameEq(condition.getUsername()),
                teamNameEq(condition.getTeamName()),
                ageGoe(condition.getAgeGoe()),
                ageLoe(condition.getAgeLoe())
            )//content 쿼리
                ,countQuery -> countQuery
                .select(member.count())
                .from(member)
                .leftJoin(member.team,team)
                .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe())
                )//count 쿼리
                );
        }

코드 분석

  • fetchResult, fetchCount 등이 사장되어서 그냥 fetchOne 버전으로 countquery까지 chain으로 받는식으로 수정했다.
    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<Long> countResult = countQuery.apply(getQueryFactory());

        return org.springframework.data.support.PageableExecutionUtils.getPage(content, pageable,
                countResult::fetchOne);
    }

출처

실전! Querydsl - 김영한 강사님
https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84