@RestController
@RequiredArgsConstructor
@Slf4j
public class ArticleController {
private final ArticleService articleService;
@GetMapping("/community/articles")
public Page<ArticleListResponse> readArticles(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size,
@RequestParam(defaultValue = "latest") String order) {
Page<Article> articlePage = articleService.findAll(page, size, order);
return articlePage.map(article -> ArticleListResponse.createResponse(article));
}
...
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class ArticleService {
private final ArticleRepository articleRepository;
public Page<Article> findAll(int page, int size, String order) {
if(order.equals("latest")) {
PageRequest pageRequest = PageRequest.of(page, size);
return articleRepository.findAllOrderByPostDate(pageRequest);
}
else if(order.equals("like")) {
PageRequest pageRequest = PageRequest.of(page, size);
return articleRepository.findAllOrderByLikeCount(pageRequest);
}
else if(order.equals("comment")) {
PageRequest pageRequest = PageRequest.of(page, size);
return articleRepository.findAllOrderByCommentCount(pageRequest);
}
return null;
}
...
}
public interface ArticleRepository extends JpaRepository<Article, Long> {
// 최신글 순으로 정렬, 페이징
@Query("select a from Article a order by a.postDate desc")
Page<Article> findAllOrderByPostDate(Pageable pageable);
// 좋아요 순으로 정렬, 페이징
@Query("select a from Article a order by a.likeCount desc, a.postDate desc")
Page<Article> findAllOrderByLikeCount(Pageable pageable);
// 댓글 순으로 정렬, 페이징
@Query("select a from Article a order by a.commentCount desc, a.postDate desc")
Page<Article> findAllOrderByCommentCount(Pageable pageable);
}
기존에는 서비스 계층에서 정렬 조건인 order를 받아서, order가 latest인지, like인지, comment인지를 구분하여, 그에 맞는 리포지토리 메서드를 호출했다.
그리고 리포지토리 메서드는 정렬(order by
)을 위해 @Query
를 사용하여 직접 JPQL을 작성해주었다.
참고로 다음과 같이 Pageable 자체에 정렬 조건을 넣을 수도 있다.
if(order.equals("recent")) {
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "postDate"));
return articleRepository.findAll(pageRequest);
}
이전 코드는 서비스 계층에서 정렬 조건을 equals()
로 확인한다. 이는 좋지 않은 코드라고 생각했고, 정렬 조건이 늘어난다면 equals()
코드도 추가해야 한다. 따라서 이를 추상화하는 방법을 찾아보다가 다음과 같이 코드를 수정해보았다.
@Getter
@RequiredArgsConstructor
public enum ArticleOrderOption {
LATEST(Sort.by(Sort.Direction.DESC, "postDate")),
LIKE(Sort.by(Sort.Direction.DESC, "likeCount").and(Sort.by(Sort.Direction.DESC, "postDate"))), // 좋아요 순으로 내림차순, 그 다음으로 최신글 순
COMMENT(Sort.by(Sort.Direction.DESC, "commentCount").and(Sort.by(Sort.Direction.DESC, "postDate"))); // 댓글 순으로 내림차순, 그 다음으로 최신글 순
private final Sort sort;
public static Sort getSortFromOrder(String order) {
return Arrays.stream(values())
.filter(orderOption -> orderOption.name().equals(order.toUpperCase(Locale.ROOT)))
.findFirst()
.orElse(LATEST)
.sort;
}
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class ArticleService {
private final ArticleRepository articleRepository;
public Page<Article> findAll(int page, int size, String order) {
Sort sort = ArticleOrderOption.getSortFromOrder(order);
PageRequest pageRequest = PageRequest.of(page, size, sort);
return articleRepository.findAll(pageRequest);
}
...
}
Sort 타입의 필드를 가지는 ArticleOrderOption이라는 enum 타입을 정의했다. sort 필드는 생성자를 통해 각 enum에 맞게 Sort가 필드로 초기화되어있다. 그리고 getSortFromOrder()
메서드로 String 형인 정렬 조건이 인자로 전달되면 그게 맞는 enum 타입이 반환된다. 따라서 서비스 계층에서 getSortFromOrder()
메서드를 호출하면서 정렬 조건을 인자로 전달하면 그에 맞는 Sort가 반환된다.
정렬 조건을 enum 타입으로 정의하는 건 생각해보지 못했는데, 훨씬 코드가 짧아지고 추상화되어 좋은 거 같다.
스프링 데이터 JPA 강의를 들으면서 컨트롤러 단에서 Pageable 형으로 페이징과 정렬 조건을 받을 수 있다는 것을 알게 되었다. 다음과 코드를 보면 api 요청을 받을 때부터 Pageable 형태로 요청을 받을 수 있음을 확인할 수 있다.
@RestController
@RequiredArgsConstructor
@Slf4j
public class ArticleController {
private final ArticleService articleService;
@GetMapping("/community/articles")
public Page<ArticleListResponse> readArticles(Pageable pageable) {
Page<Article> articlePage = articleService.findAll(pageable);
return articlePage.map(article -> ArticleListResponse.createResponse(article));
}
...
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class ArticleService {
private final ArticleRepository articleRepository;
public Page<Article> findAll(Pageable pageable) {
return articleRepository.findAll(pageable);
}
...
}
좋아요 갯수로 내림차순 정렬을 하고 싶으면 다음과 같이 api를 호출하면, 스프링 데이터 JPA는 쿼리파라미터 값을 확인하여 알아서 PageRequest 객체를 생성해준다.
http://localhost:8080/community/articles?page=0&size=3&sort=likeCount,desc
만약 다중 조건을 걸고 싶으면 다음과 같이 &
을 사용하면 된다.
http://localhost:8080/community/articles?page=0&size=3&sort=likeCount,desc&sort=createdDate,desc
그리고 디폴트 옵션을 주고 싶으면 다음과 같이 @PageableDefault
어노테이션을 사용하면 된다.
@RestController
@RequiredArgsConstructor
@Slf4j
public class ArticleController {
private final ArticleService articleService;
@GetMapping("/community/articles")
public Page<ArticleListResponse> readArticles(@PageableDefault(size = 5, sort = "createdDate",
direction = Sort.Direction.DESC) Pageable pageable) {
Page<Article> articlePage = articleService.findAll(pageable);
return articlePage.map(article -> ArticleListResponse.createResponse(article));
}
...
}
Reference