Specification을 이용한 페이징 + 필터링 조회 구현

CHOI YUN HO·2022년 10월 11일
0

SW Maestro

목록 보기
10/13

소프트웨어 마에스트로에서 "온라인 강의 큐레이션 서비스 - curady"를 개발하며 생긴 일

지난 번에 강의 목록을 조회할 때 페이징을 적용했다.(목록 조회 시 페이지네이션)

이제 여기에 아래와 같이 필터링이 적용되어야 한다.

난이도는 다중선택이 가능하다.
그래서 우선 쿼리스트링으로 입력을 받기로 했다.

/lectures/level=1,2&price=50000

위와 같이 요청이 오면, 난이도가 1,2이고 가격은 50000이하인 강의들만 반환해주어야 한다.

Controller

@GetMapping("/lectures")
    public LecturesResult<ResponseLectures> getLectures(Pageable pageable,
                                                           @RequestParam(required = false) Map<String, String> filterKeys) {
        return lectureService.getLectures(pageable, filterKeys);
    }

컨트롤러에서 Map을 이용하여 파라미터를 받았다. 이는 서비스로 넘겨서 값의 유무에 따라 로직을 수행할 것이다.

Specification

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을 사용했다.

Repository

public interface LectureRepository extends JpaRepository<Lecture, Long>, JpaSpecificationExecutor<Lecture> {
    Page<Lecture> findAll(Specification<Lecture> specification, Pageable pageable);
    ...

Repository에 JpaSpecificationExecutor를 추가하여, 위와 같이 작성했다.

Service

    @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을 통해 쿼리 조건을 추가해주었다.

profile
가재같은 사람

0개의 댓글