Querydsl Repository Support 활용

Posted by Yun on 2020-02-29

개발을 진행할 하다 보면 다양한 조회 쿼리를 만들게 됩니다. JPA를 이용한다면 Querydsl Support를 사용하면 세부 구현체를 숨기고 Repository를 통해서 조회 쿼리를 제공해 줄 수 있어 장점이 있습니다.

관련 step-15: Querydsl를 이용해서 Repository 확장하기 (1), JPA 선호하는 패턴을 참고해 주세요

QuerydslRepositorySupport 사용시 문제점

QuerydslRepositorySupport은 JPQLQuery를 이용해서 JPQL 작업을 진행합니다. 그래서 약간 단점이 있습니다.

1
2
3
4
5
6
7
8
9
10
import com.example.querydsl.domain.QPayment.payment as qPayment

class PaymentCustomRepositoryImpl : QuerydslCustomRepositorySupport(Payment::class.java), PaymentCustomRepository {

override fun findUseFrom(targetAmount: BigDecimal): List<Payment>? {
return from(qPayment)
.where(qPayment.amount.gt(targetAmount))
.fetch()
}
}

해당 코드를 보면 from으로 시작해야 합니다. QuerydslRepositorySupport 구현체의 from 메서드는 아래와 같습니다.

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

JPQLQuery을 사용해서 쿼리 작업을 진행해야 되기 때문에 from으로 시작할 수밖에 없습니다.

사실 별거 아닌 거 같지만 우리는 일반적으로 query를 시작할 때는 select으로 시작합니다. queryFactory을 이용하면 select, selectFrom으로 쿼리를 시작할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
override fun findUseSelectFrom(targetAmount: BigDecimal): List<Payment> {
return selectFrom(qPayment)
.where(qPayment.amount.gt(targetAmount))
.fetch()
}

override fun findUseSelect(targetAmount: BigDecimal): List<Long> {
return select(qPayment.id)
.from(qPayment)
.where(qPayment.amount.gt(targetAmount))
.fetch()
}

selectFrom은 조회하는 타입이 일치하는 경우 사용할 수 있고, select는 리턴 받을 타입을 지정할 수 있습니다. 우리가 일반적으로 사용하는 SQL 문과 아주 유사한 구조입니다.

QuerydslCustomRepositorySupport 작성하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class QuerydslCustomRepositorySupport(domainClass: Class<*>) : QuerydslRepositorySupport(domainClass) {

private var queryFactory: JPAQueryFactory by Delegates.notNull()

@PersistenceContext
override fun setEntityManager(entityManager: EntityManager) {
super.setEntityManager(entityManager)
this.queryFactory = JPAQueryFactory(entityManager)
}

protected fun <T> select(expr: Expression<T>): JPAQuery<T> {
return queryFactory.select(expr)
}

protected fun <T> selectFrom(from: EntityPath<T>): JPAQuery<T> {
return queryFactory.selectFrom(from)
}
}

사용법은 간단합니다. QuerydslRepositorySupport을 상속하는 클래스를 직접 정의해서 제공하는 것입니다.

코드는 간단합니다. EntityManager을 상위 클래스에서 전달해 주며 JPAQuery에서 제공해 주는 select, selectFrom을 구현(JPAQuery의 메서드를 복붙)하면 됩니다. select, selectFrom 외에도 다양한 기능들을 제공해 줄 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
internal class PaymentRepositoryTest(
private val paymentRepository: PaymentRepository
) : SpringBootTestSupport() {

@Test
internal fun `findUseSelectFrom`() {
//given
val targetAmount = 200.toBigDecimal()

//when
val payments = paymentRepository.findUseSelectFrom(targetAmount)

//then
then(payments).anySatisfy {
then(it.amount).isGreaterThan(targetAmount)
}
}

@Test
internal fun `findUseSelect`() {
//given
val targetAmount = 200.toBigDecimal()

//when
val ids = paymentRepository.findUseSelect(targetAmount)

//then
then(ids).hasSizeGreaterThan(1)
}
}

테스트 코드를 통과로 정상 동작하는 것을 확인할 수 있습니다.
select, selectFrom 시작해서 큰 이득을 얻을 수 없지만 최대한 SQL스럽게 코드를 작성할 수 있어서 개인적으로 선호하는 방식입니다.