❗문제 : Spring boot 에서 Pageable 을 통해 page, size, order 등 페이징에 대한 정보를 받아 오려고 하는데, sort 의 property 입력이 어려워요!
테스트 해보겠습니다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping(value = "/pageable")
@RestController
public class PageableController {
@GetMapping
public void getWithPageable(Pageable pageable) {
// Page Number : 0, Page Size : 20, Sort : UNSORTED
log.info("Page Number : {}, Page Size : {}, Sort : {}",
pageable.getPageNumber(),
pageable.getPageSize(),
pageable.getSort());
}
}
http://localhost:8080/pageable
Pageable 에는 기본(Default) PageNumber, PageSize, Sort 가 있다.
그렇다면 Pageable 은 어떻게 파라미터를 받을까?
테스트 해보겠습니다.
<http://localhost:8080/pageable?page=2&size=35>
-> Page Number : 2, Page Size : 35, Sort : UNSORTED
이로써 Pageable 의 PageNumber 는 page, PageSize 는 size 라는 것을 알 수 있다.
Sort 는 어떻게 받을까?
Pageable 인터페이스에는 Sort getSort(); 가 정의되어 있다. 위의 Sort 객체를 찾아가보면 List<Order> 를 필드로 가지며 Order 는 Direction 과 property 로 이루어져 있는 것을 알 수 있다.
테스트 해보겠습니다.
<http://localhost:8080/pageable?page=2&size=35&sort=nickname,ASC>
-> Page Number : 2, Page Size : 35, Sort : nickname: ASC
<http://localhost:8080/pageable?page=2&size=35&sort=nickname,ASC&sort=phone,DESC>
-> Page Number : 2, Page Size : 35, Sort : nickname: ASC,phone: DESC
<http://localhost:8080/pageable?page=2&size=35&sort=create,name>
-> Page Number : 2, Page Size : 35, Sort : create: ASC,name: ASC
이로써 Pageable 의 Sort 는 sort 라는 파라미터 명의 , 로 구분한 property,direction 형식이라는 것을 알 수 있다.
아까 궁금했던 문제를 살펴보자.
Spring boot 에서 Pageable 을 통해 page, size, order 등 페이징에 대한 정보를 받아 오려고 하는데,
sort 의 property 입력이 어려워요!
저기서 입력이 어렵다는 말은 property 는 String 을 사용하여 parameter 를 받고,
입력받은 property 를 가지고 Query 문에서 Select 하기 때문이다.
무슨말인지는 코드를 보자
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RequestMapping(value = "/pageable")
@RestController
public class PageableController {
private final PageableService pageableService;
@GetMapping
public void getWithPageable(Pageable pageable) {
pageableService.retrieve(pageable);
}
}
@RequiredArgsConstructor
@Service
public class PageableService {
private final PageableRepository pageableRepository;
public void retrieve(Pageable pageable){
pageableRepository.findAll(pageable);
}
}
위 코드와 같이 클라이언트에서 직접 받은 파라미터를 Select 문에 바로 사용한다고 하면,
이처럼 클라이언트가 어떤 테이블의 어떤 파라미터로 Sort 할 것인지를 알아야 한다는 점이다.
이 포인트를 해결 할 인터페이스 를 정의 해봤다.
- 클라이언트가 보내 줄 Sort Property 를 정의한다.
- 전달 받은 Sort Property 를 검색하려는 테이블에 맞는 String 으로 변화한다.
@Getter
public enum RestrictPageableProperty {
USER_NAME("user.name"),
COMPANY_PHONE("company.phone"),
;
private final String tableProperty;
RestrictPageableProperty(String tableProperty) {
this.tableProperty = tableProperty;
}
private static final Map<String, RestrictPageableProperty> nameMap = Arrays.stream(RestrictPageableProperty.values())
.collect(Collectors.toUnmodifiableMap(RestrictPageableProperty::name, Function.identity()));
public static RestrictPageableProperty get(String name) {
return nameMap.get(name);
}
}
1. 위에서 테스트한 Pageable 인터페이스 중 Sort 파라미터를 처리하는 SortHandlerMethodArgumentResolver 를 모방한 Resolver
public class RestrictPageableSortArgumentResolver extends SortHandlerMethodArgumentResolverSupport implements SortArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Sort.class.equals(parameter.getParameterType());
}
@Override
public Sort resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
String[] directionParameter = webRequest.getParameterValues(this.getSortParameter(parameter));
if (directionParameter == null) {
return this.getDefaultFromAnnotationOrFallback(parameter);
} else {
return directionParameter.length == 1 && !StringUtils.hasText(directionParameter[0]) ? this.getDefaultFromAnnotationOrFallback(parameter) : this.parseParameterIntoSort(Arrays.asList(directionParameter), this.getPropertyDelimiter());
}
}
private Sort parseParameterIntoSort(List<String> source, String delimiter) {
List<Sort.Order> allOrders = new ArrayList<>();
for (String part : source) {
if (part != null) {
allOrders.add(getOrder(part, delimiter));
}
}
return allOrders.isEmpty() ? Sort.unsorted() : Sort.by(allOrders);
}
private Sort.Order getOrder(String sortByQuery, String delimiter) {
String[] splitByDelimiter = sortByQuery.split(delimiter);
final String property = splitByDelimiter[0];
final String direction = splitByDelimiter.length == 1 ? Sort.Direction.ASC.name() : splitByDelimiter[1];
return RestrictPageableOrder.of(property, direction).toOrder();
}
}
💡 이 Resolver 는 기본 Pageable 핸들링을 위한 Sort Resolver 로 등록 해줘야 함
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new PageableHandlerMethodArgumentResolver(new RestrictPageableSortArgumentResolver()));
}
}
2. Select 를 위한 Table Property 버전의 Sort 를 가져온다.
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class RestrictPageableOrder {
private final RestrictPageableProperty property;
private final Sort.Direction direction;
public static RestrictPageableOrder of(String property,
String direction) {
return new RestrictPageableOrder(
RestrictPageableProperty.get(property.toUpperCase()),
Sort.Direction.valueOf(direction.toUpperCase())
);
}
private boolean unknownOrder() {
return Objects.isNull(direction) || Objects.isNull(property);
}
public Sort.Order toOrder() {
if (unknownOrder()) {
throw new RuntimeException();
}
return new Sort.Order(direction, property.getTableProperty());
}
}
이렇게 만들어 놓고 테스트를 해보자
<http://localhost:8080/pageable?page=2&size=35&sort=nickname,ASC&sort=phone,DESC>
-> **ERROR** ( 정의 되지 않은 Property 를 사용하면 에러가 난다. )
<http://localhost:8080/pageable?page=2&size=35&sort=USER_NAME,ASC>
-> Page Number : 2, Page Size : 35, Sort : user.name: ASC
<http://localhost:8080/pageable?page=2&size=35&sort=USER_NAME,ASC&sort=COMPANY_PHONE,DESC>
-> Page Number : 2, Page Size : 35, Sort : user.name: ASC,company.phone: DESC
위 테스트와 같은 결과를 얻을 수 있다.
이로써 RestrictPageableResolver 의 역할은 두가지가 되었다.
- Sort 할 수 있는 Table Property 를 알 필요 없이 정해진 Enum 에 대해서만 요청하면 된다.
- 정해진 Enum 으로 제한하여 지원할 Sort Property 를 정의 할 수 있다.
읽어주셔서 감사합니다.
'학습 > TIL' 카테고리의 다른 글
RequestParam, RequestBody, MyBatis 는 어떻게 객체를 매핑할까? (0) | 2022.08.02 |
---|