현재 진행중인 프로젝트는 두 가지 검색 조건이 있는데
이 조건들이 만드는 경우의 수는 4가지로 기존 JPA 쿼리메소드 4가지를 작성해줘야하고 service단에서도 분기를 작성해 줘야하는 문제가 생겼다.
지금은 조건이 2가지밖에 없어 작성이 어렵진 않겠지만 조건이 더 많아진다면..? 😐
하여 QueryDSL을 사용하여 동적 쿼리를 작성해보기로 했다.
QueryDsl은 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 오픈소스 프레임워크이다.
등등 적용시에 공부해야할 점은 조금 있었지만 적용 후 꽤나 깔끔하고 만족스러웠다
Querydsl을 사용하는 여러가지 방법이 있었는데, 나는 그중에서 interface
를 작성해 구현하고 repository
에서 상속받아 사용하는 방식을 택했다.
이유는 entity당 하나의 repository만 의존성으로 받아 사용하고 싶었기 때문에
public interface QueryDSLRepository {
Page<Work> findByUserAndPeriodAndEmployee(User user, LocalDate dateFrom, LocalDate dateTo, Long employeeId, Pageable pageable);
}
인터페이스를 작성해주었다.
@Repository
@RequiredArgsConstructor
public class WorkRepositoryImpl implements QueryDSLRepository {
private final JPAQueryFactory jpaQueryFactory;
@Override
public Page<Work> findByUserAndPeriodAndEmployee(User user, LocalDate dateFrom, LocalDate dateTo, Long employeeId, Pageable pageable) {
List<Work> content = jpaQueryFactory
.selectFrom(work)
.where(userEq(user), dateBetween(dateFrom, dateTo), employeeIdEq(employeeId))
.orderBy(work.date.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> count = jpaQueryFactory
.select(work.count())
.from(work)
.where(userEq(user), dateBetween(dateFrom, dateTo), employeeIdEq(employeeId));
return PageableExecutionUtils.getPage(content, pageable, count::fetchOne);
}
private BooleanExpression userEq(User user) {
return work.employee.user.eq(user);
}
private BooleanExpression dateBetween(LocalDate dateFrom, LocalDate dateTo) {
if (dateFrom == null & dateTo == null) {
return null;
}
return work.date.between(dateFrom, dateTo);
}
private BooleanExpression employeeIdEq(Long employeeId) {
if(employeeId == null) {
return null;
}
return work.employee.id.eq(employeeId);
}
}
where절의 동적쿼리는 BooleanExpression
을 사용해 작성하는것이 재사용성, 가독성, 안정성 면에서 좋다고 한다.
fetchCount()
와 fetchResult()
는 deprecated 되어 사용할 수 없다.
Count쿼리 최적화를 위해서 count
쿼리를 별도로 JPAQuery
를 작성하고
반환 객체로 PageImpl
을 사용하지 않고 PageableExecutionUtils
를 사용하였다.
PageableExecutionUtils.getPage(content, pageable, count::fetchOne)
시에는 count쿼리를 필요한 경우에만 날리기 때문에 최적화가 가능하다!!