ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 20221223 TIL 게시글 인기순으로 정렬하기
    TIL 2022. 12. 23. 17:59

    https://labs.bluesoft.com.br/wp-content/uploads/2019/09/JPA-hibernate.jpg

    JPQL을 이용해서 게시글을 인기순으로 정렬하여 가져오는 방식으로 코드를 작성해 두었었는데,

    검색 조건이 여러가지 붙게 되면서 JPA Specification을 이용해서 리팩토링을 하게 되었다.

    그래서 JPQL이 아닌 다른 방식으로 게시글을 인기순으로 정렬해야 하는 상황을 마주하게 되었다.

     

    쉽사리 어떻게 해결을 해야할지 감이 잘 잡히지 않았었는데,

    가장 먼저 떠오른 생각은 Specification 중 하나로 추가하는 것이었다.

    그래서 뭔가 이상하지만 일단 TDD의 Green단계를 진행하고 있다는 생각으로 어떻게든 돌아가게끔 작성을 해보았다.

    // specifications/QuestionSpecification.java
    
        public static Specification<Question> sortBy(String sort) {
            if (sort.equals("like")) {
                return new Specification<Question>() {
    
                    @Override
                    public Predicate toPredicate(Root<Question> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                        query.orderBy(criteriaBuilder.desc(criteriaBuilder.size(root.<Collection>get("likeUserIds"))));
                        return criteriaBuilder.equal(criteriaBuilder.literal(1), 1);
                    }
                };
            }
    
            return (root, query, criteriaBuilder) -> null;
        }

    만약 sort가 "like", 즉 인기순으로 정렬하라는 요청이 오면

    Question엔티티의 likeUserIds의 size의 역순으로 정렬하게끔 하는 specification이다.

    위와 같이 Specification을 정의해주고, 아래와 같이 specification에 and처리를 해주어

    findAll 메서드에 인자로 specification을 넘겨주면 좋아요 순으로 정렬되어 반환된 값을 받을 수 있다.

    // application/GetQuestionsService.java
    
            if (sort != null) {
                specification = specification.and(QuestionSpecification.sortBy(sort));
            }
            
            return questionRepository.findAll(specification, pageable);

     

    그런데 specification은 정렬을 위한 것이 아니고, 검색 조건을 위한 것이다.

    따라서 Specification 코드에서도 매우 이상한 부분이 발생할 수 밖에 없었다.

    바로 return criteriaBuilder.equal(criteriaBuilder.literal(1), 1); 부분이다.

    그 코드는 그냥 필터링을 하지 않겠다는 뜻이다.

    즉, 함수형 코드에서 filter(i => true)와 동일한 코드이다.

    나는 정렬을 위한 Specification코드를 작성한 것이기 때문에, filtering이 필요가 없어서 위와 같이 이상한 코드가 발생하였다.

    하지만 다른 뾰족한 수가 떠오르지 않았기 때문에 일단 찝찝하지만 이 상태로 어제는 시간이 늦어 마무리를 지었다.

     

    정렬은 Sort를 이용하라는 글들을 많이 볼 수 있었다.

    Sort를 사용할 수 있었다면 바로 사용했겠지만 Sort를 사용하지 못했던 이유는 Sort는 엔티티의 필드 값을 기준으로 정렬하게 되는데,

    나는 Question이라는 엔티티에 likeUserIds라는 콜렉션 값을 들고 있는 필드를 가지고 있었고,

    필드 값 기준이 아닌 필드 값의 길이를 기준으로 정렬을 해야했기 때문에 Sort를 어떻게 사용하면 좋을지 감이 잡히지 않았다.

     

    @Query와 specification을 동시에 사용할 수 없기 때문에

    여러 방법을 찾아봤지만, Sort를 이용하는 게 최선이라는 생각이 들었고,

    Sort를 이용하려면 countOfLikes라는 필드가 하나 더 있을 수 밖에 없다고 생각했다.

    그런데 이 필드는 굳이 DB에 따로 저장을 하고 싶지는 않았다.

    그래서 방법들을 찾아보았는데, 하이버네이트가 제공하는 @Formula 어노테이션으로  네이티브 SQL을 사용해서

    DB에 실제로 존재하지는 않는 가상 컬럼을 만들 수 있다는 것을 알게 되었다.

    // models/Question.java
    
        @Formula("(SELECT COUNT(*) FROM question_like_user_ids l WHERE l.question_id = id)")
        private int countOfLikes;

    위와 같이 해당 질문의 좋아요 수에 해당하는 가상 컬럼을 하나 만들었고,

    // application/GetQuestionsService.java
    
    if (sort.equals("like")) {
        sortBy = Sort.by("countOfLikes").descending();
    }

    위와 같이 좋아요 순으로 정렬을 할 수 있게 되었다!

     

    이로써 리팩토링 과정까지 마무리를 하여 인기순으로 게시물을 정렬할 수 있게 되었다.

    다양한 방식으로 동일한 기능을 구현해보니 시각을 넓힐 수 있어 좋았던 것 같다.

    그리고 마음에 들진 않더라도 일단 돌아가게 만들고 나니 리팩토링을 마음 놓고 진행할 수 있어 좋았다.

    만약 처음부터 제대로된 코드를 짤려고 했다면 아직도 완성을 하지 못했을 것 같다.

    어려울수록 GREEN과정을 더 제대로 밟고 리팩토링을 하는 방식으로 작업하면 좋을 것 같다!

    댓글

Designed by Tistory.