📌 정적 쿼리
📌 동적 쿼리
MatchingController
@GetMapping("/list/filter")
public String filterMatching(@RequestParam(name = "genretype", required = false) String genreTypeStr,
@RequestParam(name = "starttime", required = false) Integer startTime,
@RequestParam(name = "gender", required = false) String gender,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "8") int size,
@RequestParam(defaultValue = "createDate") String sortCode,
@RequestParam(defaultValue = "DESC") String direction,
Model model) {
GenreTagType genreType = null;
if (genreTypeStr != null && !genreTypeStr.isEmpty()) {
genreType = GenreTagType.ofCode(genreTypeStr);
}
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(direction), sortCode));
Page<Matching> matchingList = matchingService.filterMatching(genreType, startTime,gender, pageable);
model.addAttribute("matchingList", matchingList);
model.addAttribute("currentPage", page);
model.addAttribute("genretype", genreTypeStr); // This line is changed
model.addAttribute("starttime", startTime);
model.addAttribute("gender", gender);
return "matching/list";
}
MatchingService
public Page<Matching> filterMatching(GenreTagType genreType, Integer startTime, String gender, Pageable pageable) {
if (genreType != null && startTime != null) {
// 장르와 시간
return matchingRepository.findByGenreAndStartTime(genreType, startTime, pageable);
} else if (genreType != null) {
// 장르
return matchingRepository.findByGenre(genreType, pageable);
} else if (startTime != null) {
// 시작시간
return matchingRepository.findByStartTime(startTime, pageable);
} else if (!gender.isEmpty()) {
// 성별
return matchingRepository.findByGender(gender, pageable);
} else if (genreType != null && gender.isEmpty()) {
// 장르와 성별
return matchingRepository.findByGenreAndGender(genreType, gender, pageable);
} else if (startTime != null && gender.isEmpty()) {
// 성별과 시간
return matchingRepository.findByStartTimeAndGender(startTime, gender, pageable);
} else if (genreType != null && startTime != null && gender.isEmpty()){
// 장르, 성별, 시간
return matchingRepository.findByGenreAndStartTimeAndGender(genreType, startTime, gender, pageable);
}
return matchingRepository.findAll(pageable);
}
MatchingRepository
public interface MatchingRepository extends JpaRepository<Matching, Long>, MatchingRepositoryCustom{
Optional<Matching> findById(Long id);
Page<Matching> findAll(Pageable pageable);
Page<Matching> findByTitleContainingIgnoreCase(String keyword, Pageable pageable);
Page<Matching> findByContentContainingIgnoreCase(String keyword, Pageable pageable);
}
MatchingRepositoryCustom
public interface MatchingRepositoryCustom {
Page<Matching> filterByGenreAndStartTimeAndGender(GenreTagType genreType, Integer startTime, String gender, Pageable pageable);
}
MatchingRepositoryImpl
@RequiredArgsConstructor
public class MatchingRepositoryImpl implements MatchingRepositoryCustom{
private final JPAQueryFactory queryFactory;
@Override
public Page<Matching> filterByGenreAndStartTimeAndGender(GenreTagType genreType, Integer startTime, String gender, Pageable pageable) {
QMatching matching = QMatching.matching;
// 필터링 조건을 담을 Predicate 변수 선언
BooleanBuilder predicate = new BooleanBuilder();
// 각각의 파라미터가 null이 아닌 경우에만 해당 필터를 추가
if (genreType != null) {
predicate.and(matching.genre.eq(genreType));
}
if (startTime != null) {
predicate.and(matching.startTime.eq(startTime));
}
if (gender != null && !gender.isEmpty()) {
predicate.and(matching.gender.eq(gender));
}
// 정렬 조건 생성
OrderSpecifier<?> orderSpecifier = sortMatching(pageable);
// fetch()와 select(Expressions.ONE).fetchOne()를 따로 사용
List<Matching> results = queryFactory.selectFrom(matching)
.where(predicate)
.orderBy(orderSpecifier) // 외부에서 정렬 조건을 넘겨받음
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
Long total = queryFactory.select(Expressions.ONE.count()).from(matching)
.where(predicate)
.fetchOne();
return new PageImpl<>(results, pageable, total);
}
private OrderSpecifier<?> sortMatching(Pageable page) {
QMatching matching = QMatching.matching;
//서비스에서 보내준 Pageable 객체에 정렬조건 null 값 체크
if (!page.getSort().isEmpty()) {
//정렬값이 들어 있으면 for 사용하여 값을 가져온다
for (Sort.Order order : page.getSort()) {
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
switch (order.getProperty()){
case "createDate":
return new OrderSpecifier(direction, matching.createDate);
}
}
}
return null;
}
}
❓BooleanBuilder와 BooleanExpression
BooleanExpression과 BooleanBuilder 모두 QueryDSL에서 제공하는 클래스로써, 동적 쿼리를 작성할 때 사용
❓BooleanExpression
조건 쿼리의 반환 타입으로, 여러 BooleanExpression들을 결합해서(AND, OR) 새로운 BooleanExpression을 생성할 수 있음
쿼리를 조건으로 변환하는데 사용되는 클래스
❓BooleanBuilder
여러 BooleanExpression들을 결합하여 하나의 조건 쿼리를 만들 때 사용
BooleanBuilder는 내부적으로 BooleanExpression을 조합하고 관리하는 역할
❗정리하자면
BooleanExpression은 BooleanBuilder에 추가될 수 있는 독립적인 조건(즉, 조건의 표현식)을 표현하는 반면, BooleanBuilder는 여러 BooleanExpression들을 관리하고 결합하여 최종적인 쿼리 조건을 생성
MatchingService
public Page<Matching> filterMatching(GenreTagType genreType, Integer startTime, String gender, Pageable pageable) {
return matchingRepository.filterByGenreAndStartTimeAndGender(genreType, startTime, gender, pageable);
}
QueryDSLConfiguration
@Configuration
public class QueryDSLConfiguration {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
✔️ Service의 코드가 훨씬 깨끗해졌다.
✔️ 실행 시, 필요한 쿼리만 출력되는 것을 볼 수 있었다.
✔️ BuildGradle에 의존성 추가할 때, 오류가 많이 발생했어서 QueryDSL이 맞게 적용된 건지 확신이 없었다.
➕ 찾아보니 QueryDSL이 적용되면 프로젝트 src → generated → Entity들에 Q가 붙은 Q클래스들이 생성되는 것을 알 수 있었고, 내 프로젝트에도 적용되어 있었다.