소프트웨어 마에스트로에서 "온라인 강의 큐레이션 서비스 - curady"를 개발하며 생긴 일
지난 번에 강의 목록을 조회할 때 페이징을 적용했다.(목록 조회 시 페이지네이션)
이제 여기에 아래와 같이 필터링이 적용되어야 한다.
난이도는 다중선택이 가능하다.
그래서 우선 쿼리스트링으로 입력을 받기로 했다.
/lectures/level=1,2&price=50000
위와 같이 요청이 오면, 난이도가 1,2이고 가격은 50000이하인 강의들만 반환해주어야 한다.
@GetMapping("/lectures")
public LecturesResult<ResponseLectures> getLectures(Pageable pageable,
@RequestParam(required = false) Map<String, String> filterKeys) {
return lectureService.getLectures(pageable, filterKeys);
}
컨트롤러에서 Map을 이용하여 파라미터를 받았다. 이는 서비스로 넘겨서 값의 유무에 따라 로직을 수행할 것이다.
public class LectureSpecification {
public static Specification<Lecture> equalLectureLevel(List<Integer> levelList) {
return new Specification<Lecture>() {
@Override
public Predicate toPredicate(Root<Lecture> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.and(root.get("level").in(levelList));
}
};
}
public static Specification<Lecture> equalLectureCategory(Long category) {
return new Specification<Lecture>() {
@Override
public Predicate toPredicate(Root<Lecture> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get("category"), category);
}
};
}
public static Specification<Lecture> betweenPrice(Integer salePrice) {
return new Specification<Lecture>() {
@Override
public Predicate toPredicate(Root<Lecture> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.between(root.get("salePrice"), 0, salePrice);
}
};
}
}
위와 같이 LectureSpecification 클래스를 추가하고 쿼리 조건을 함수로 만들어줬다.
난이도(level)은 다중 선택이 가능했기에, 리스트로 받아서 IN 쿼리를 사용하도록 했고,
category는 equal, 가격은 between을 사용했다.
public interface LectureRepository extends JpaRepository<Lecture, Long>, JpaSpecificationExecutor<Lecture> {
Page<Lecture> findAll(Specification<Lecture> specification, Pageable pageable);
...
Repository에 JpaSpecificationExecutor를 추가하여, 위와 같이 작성했다.
@Transactional(readOnly = true)
public LecturesResult<ResponseLectures> getLectures(Pageable pageable,
Map<String, String> filterKeys) {
Specification<Lecture> specification = (root, query, criteriaBuilder) -> null;
if (filterKeys.get("category") != null) {
categoryRepository.findById(Long.valueOf(filterKeys.get("category"))).orElseThrow(CategoryNotFoundException::new);
specification = specification.and(LectureSpecification.equalLectureCategory(Long.valueOf(filterKeys.get("category"))));
}
if (filterKeys.get("level") != null) {
List<Integer> levelList = new ArrayList<>();
for (String s : filterKeys.get("level").split(",")) {
levelList.add(Integer.valueOf(s));
}
specification = specification.and(LectureSpecification.equalLectureLevel(levelList));
}
if (filterKeys.get("price") != null) {
specification = specification.and(LectureSpecification.betweenPrice(Integer.valueOf(filterKeys.get("price"))));
}
Page<Lecture> lecturePage = lectureRepository.findAll(specification, pageable);
List<ResponseLectures> responseLectures =
LectureMapper.INSTANCE.lecturesToResponseList(lecturePage.getContent());
return responseService.getLecturesResult(lecturePage.getTotalPages(), responseLectures);
}
컨트롤러에서 Map으로 넘겨준 파라미터에서 값의 유무에 따라 specification을 통해 쿼리 조건을 추가해주었다.