QueryDSL 지원 클래스 만들기

Gyeongjae Ham·2023년 6월 18일
0

QueryDSL

목록 보기
5/5
post-thumbnail

해당 시리즈는 김영한님의 JPA 로드맵을 따라 학습하면서 내용을 정리하는 글입니다

사용하는 이유

  • QuerydslRepositorySupport가 제공하는 기능들은 좋았지만 메소드 체인이 풀리는 단점과 FROM 절로 시작해야 하는 가독성 문제, 그리고 sort 기능이 완전하지 않은 치명적인 단점들이 존재합니다
  • 따라서 고급 내용이기는 하지만 추후 프로젝트에 코드량을 줄이기 위한 커스텀을 위해서 기능을 직접 구현하는 클래스를 만들겠습니다

QueryDSL을 지원하는 추상클래스 생성하기

/**
 * Querydsl 4.x 버전에 맞춘 Querydsl 지원 라이브러리
 * @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);
    }
}

지원하는 추상 클래스를 상속받은 레포지토리 예제

@Repository
public class MemberTestRepository extends Querydsl4RepositorySupport {
	// 생성자에 타겟 엔티티를 넣어줍니다
    public MemberTestRepository() {
        super(Member.class);
    }

    public List<Member> basicSelect() {
    	// QueryFactory 생성 없이 바로 작성할 수 있게 됩니다
        return select(member)
                .from(member)
                .fetch();
    }

    public List<Member> basicSelectFrom() {
    	// QueryFactory 생성 없이 바로 작성할 수 있게 됩니다
        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()));

		// 페이징 처리를 해주는 부분입니다(offset, limit) 처리를 알아서 처리해줍니다
        List<Member> content = getQuerydsl().applyPagination(pageable, query)
                .fetch();
        return PageableExecutionUtils.getPage(content, pageable, query::fetchCount);
    }

	// searchPageByApplyPage와 동일한 기능을 합니다
    // getQuerydsl().applyPagination 부분을 내부적으로 처리하고 있는 메소드를 사용했습니다
    public Page<Member> applyPagination(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())));
    }

	// 앞 선 학습에서 나왔던 searchComplex에서 조회하는 쿼리와 카운트 쿼리를 분리했었습니다
    // 위 예제의 두 쿼리를 하나의 메서드로 합쳐서 구현한 부분입니다(람다식으로 구현)
    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
                        .selectFrom(member)
                        .leftJoin(member.team, team)
                        .where(usernameEq(condition.getUsername()),
                                teamNameEq(condition.getTeamName()),
                                ageGoe(condition.getAgeGoe()),
                                ageLoe(condition.getAgeLoe()))
        );
    }

	// 아래 메서드들은 동적 쿼리를 WHERE절의 다중 파라미터로 해결하기 위한 메서드 구현입니다 
    private BooleanExpression usernameEq(String username) {
        return isEmpty(username) ? null : member.username.eq(username);
    }

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

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

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe == null ? null : member.age.loe(ageLoe);
    }
}
profile
Always be happy 😀

0개의 댓글