https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl
안녕하세요..!
오늘은 Spring Framework를 활용하여 검색조회 필터링을 구현하는 방법에 대해 소개할게요! 주어진 코드는
특정 조건에 따라 Course 객체를 필터링하고, 결과를 DTO로 변환하여 반환하는 기능을 위해서 제가 진행한 프로젝트에서 필터링 기능을 적용하기 위해서 사용했습니다!
이 과정에서 JPA의 Specification을 사용하여 동적 쿼리를 생성함으로써 필터링 기능을 작동시킬수 있습니다!
1. Specification이란?
Specification은 JPA에서 제공하는 인터페이스로, 동적 쿼리를 생성하는 데 유용합니다! . 주로 복잡한 검색 조건을 처리할 때 사용되며, Criteria API와 함께 활용될 수 있습니다. Specification을 사용하면 다음과 같은 장점이 있어요:
- 재사용성: 여러 쿼리에서 동일한 조건을 재사용할 수 있어, 코드의 중복을 줄일 수 있어요.
- 유연성: 동적으로 조건을 추가하거나 변경할 수 있어, 다양한 검색 요구를 쉽게 처리할 수 있어요!
- 가독성: 명확한 구조로 쿼리 조건을 표현할 수 있어, 코드의 가독성이 높일수 있습니다!
그럼 바로 코드로 보면서 설명을 해보겠습니다!
1. 코드 설명
1.1. getAllCourses 메서드
public CourseGetAllRes getAllCourses(final CourseGetAllReq courseGetAllReq) {
Specification<Course> spec = CourseSpecifications.filterByCriteria(courseGetAllReq);
List<Course> courses = courseRepository.findAll(spec);
List<CourseDtoGetRes> courseDtoGetResList = convertToDtoList(courses, Function.identity());
return CourseGetAllRes.of(courseDtoGetResList);
}
- 입력 매개변수: CourseGetAllReq 객체를 통해 검색 조건을 전달받아요.
- 입력 조건에는 저는 다음과 같이 받을수 있었습니다!
- Country (대분류) , city (소분류) , cost (가격)
- 각각의 값은 있을수도 null일수도 있습니다!
- 입력 조건에는 저는 다음과 같이 받을수 있었습니다!
- Specification 생성: CourseSpecifications.filterByCriteria(courseGetAllReq)를 호출하여 필터링 조건을 기반으로 하는 Specification 객체를 생성한답니다.
- 쿼리 실행: courseRepository.findAll(spec)를 통해 필터링된 Course 목록을 데이터베이스에서 조회해요
List<Course> courses = courseRepository.findAll(spec);
- DTO 변환: 조회된 Course 객체를 DTO 리스트로 변환해요.
- 결과 반환: 변환된 DTO 리스트를 포함하는 CourseGetAllRes 객체를 반환한답니다.
1.2. CourseSpecifications 클래스
public class CourseSpecifications {
public static Specification<Course> filterByCriteria(CourseGetAllReq courseGetAllReq) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
addPredicate(predicates, criteriaBuilder, root, "country", courseGetAllReq.country(),
criteriaBuilder::equal);
// ... (중략)
predicates.removeIf(Objects::isNull);
query.orderBy(criteriaBuilder.desc(root.get("createdAt")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}
- Specification: filterByCriteria 메서드는 CourseGetAllReq의 조건에 맞춰 Predicate 리스트를 생성하여 필터링 조건을 설정해요.
- 조건 추가: addPredicate 및 addCostPredicate 메서드를 통해 다양한 필터 조건을 추가한답니다.
- 정렬: 결과는 createdAt 필드를 기준으로 내림차순 정렬돼요.
1.3. 필터 조건 추가 메서드
- addPredicate 메서드: 주어진 속성과 값에 따라 Predicate를 생성하고, null이 아닐 경우 predicates 리스트에 추가해요.
private static <T> void addPredicate(List<Predicate> predicates, CriteriaBuilder criteriaBuilder, Root<?> root,
String attributeName, T value,
BiFunction<Path<T>, T, Predicate> predicateFunction) {
Optional.ofNullable(value)
.ifPresent(val -> {
Predicate predicate = predicateFunction.apply(root.get(attributeName), val);
if (predicate != null) {
predicates.add(predicate);
}
});
}
- addCostPredicate 메서드: 비용 조건을 기반으로 여러 가지 경우에 따라 Predicate를 추가해요. 특정 비용 범위에 따라 필터링을 수행한답니다.
- 여기서는 클라이언트 개발자와 논의해 3,5,10만원 이하, 10만원 이상을 구현하기 위해서 각값들을 고정으로 넣어준것입니다!
private static void addCostPredicate(List<Predicate> predicates, CriteriaBuilder criteriaBuilder, Root<?> root,
Integer cost) {
if (cost != null) {
switch (cost) {
case 3:
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("cost"), 30000));
break;
case 5:
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("cost"), 50000));
break;
case 10:
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("cost"), 100000));
break;
case 11:
predicates.add(criteriaBuilder.greaterThan(root.get("cost"), 100000));
break;
default:
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("cost"), cost));
break;
}
}
}
2. 작동 원리
- 요청 처리: 클라이언트가 검색 요청을 보내면 getAllCourses 메서드가 호출된답니다.
- 조건 생성: 요청 데이터를 바탕으로 Specification을 생성해요.
- 쿼리 실행: 생성된 Specification을 사용하여 데이터베이스에서 필터링된 결과를 조회해요.
- 결과 반환: 최종적으로 DTO 리스트를 생성하여 클라이언트에게 반환한답니다.
혹시 Specifiaction에 대해서 더 궁금하시다면 ? 다음 문서를 참고해보시고 필터링에 대해서 더 완벽히 파고들어 봅시다!
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl
Advanced Spring Data JPA - Specifications and Querydsl
In my last blog post I introduced the basic feature set of Spring Data JPA. In this post I'd like to dive into some more features and how they can help you simplify data access layer implementation even further. The Spring Data repository abstraction consi
spring.io
'Spring' 카테고리의 다른 글
[Spring] Redis Stream을 사용한 EventPublisher, EventListener 구현 (0) | 2024.08.05 |
---|---|
Swagger로 사랑받는 개발자 되기 [ Spring ] (0) | 2024.08.01 |
편리한 객체간 매핑을 위한 MapStruct 적용기 .feat 당근클론코딩 (1) | 2024.05.08 |
JPA - 단방향? 양방향? OneToMany? ManyToOne? (3) | 2024.04.29 |
@ManytoOne, @ManytoMany, @OnetoOne, @OnetoMany? (2) | 2024.04.28 |