ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [내일배움캠프] 3월 8일 수요일 TIL 회고록
    카테고리 없음 2023. 3. 9. 00:03

    댓글 전체 조회, 유저 본인이 쓴 댓글을 볼 수 있는 기능에 Querydsl 기능을 추가해봤다.

     

    먼저 dto 폴더에 CommentPageDto 클래스랑 CommentSearchCond 클래스를 만들었다.

     

    CommentPageDto.java

    @RequiredArgsConstructor
    @AllArgsConstructor
    @Builder
    @Setter
    @Getter
    public class CommentPageDto {
    
      @Positive // 0보다 큰수
      private int page = 1;
    
      private int size = 5;
      private String sortBy;
    
    
      public Pageable toPageable() {
        if (Objects.isNull(sortBy)) {
          return PageRequest.of(page - 1, size);
        } else {
    //      Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
          return PageRequest.of(page - 1, size, Sort.by(/*direction,*/ sortBy).descending());
        }
      }
    
      public Pageable toPageable(String sortBy) {
    
        return PageRequest.of(page - 1, size, Sort.by(sortBy).descending());
      }
    }

    Querydsl 페이징 기능에 사용 될 PageDto를 만들었다. 

     

    CommentSearchCond.java

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @SuperBuilder
    public class CommentSearchCond {
    
      // 댓글로 검색..
      private String content;
      // 이름으로 검색..
      private String nickname;
      private CommentSort commentSort;
      private CommentSortDirection commentSortDirection;

    검색기능에 필요한 필드를 만들었다. (아마도..) 보통 다른 커뮤니티 사이트 검색기능을 보면 댓글로 검색 기능은 있고 이름으로 검색 기능도 있어서 content(댓글), nickname(이름) 필드를 만들었다. 사실 nickname이 댓글 검색 기능에 있어야 해야할지 아리송하다. 

    그리고 CommentSort, CommentSortDirection 를 받는 필드를 만들었다.

     

    CommentSort.java

    public enum CommentSort {
    
      EMPTY,
      COMMENT
    
    }

    enum 클래스로 만들었고, 검색을 했을때 비어있어도 검색이 되게 만들었다. COMMENT도 넣었다.

     

     

    CommentSortDirection.java

    public enum CommentSortDirection {
    
      ASC,
      DESC
    }
    오름차순, 내림차순으로 정렬되기에 ASC와 DESC를 넣었다.

     

    CommentReposiotryCustom.java

    public interface CommentRepositoryCustom {
    
      Page<CommentResponse> searchComment(CommentPageDto commentPageDto,
          CommentSearchCond commentSearchCond);
    
      Page<CommentResponse> searchMyComment(CommentPageDto commentPageDto,
          CommentSearchCond commentSearchCond, Long userId);
    
    }

    그 다음 인터페이스 클래스로 CommentRepositoryCustom 클래스를 만들었다.

    페이징 처리로 할거니까 Page로 리턴타입을 만들었고 매개변수로 아까 만든 CommentPageDto, CommentSearchCond를 넣었다.

    SearchComment는 댓글 전체 조회(검색 가능)이고 SearchMyComment는 유저 본인이 쓴 댓글들을 볼 수 있는 기능이다. (검색 가능)

     

    원래 있던 CommentRepository에 CommentRepositoryCustom을 extends에 추가했다. (확장시켰다(상속시켰다))

    public interface CommentRepository extends JpaRepository<Comment, Long>, CommentRepositoryCustom {

    그 다음 Repository 폴더에 인터페이스 타입 CommentRepositoryCustom을 상속받는 클래스 타입 CommentRepositoryImpl 클래스를 만들었다.

    public class CommentRepositoryImpl implements CommentRepositoryCustom {

     

    먼저 전체 조회 기능부터 만들었다.

    Projections.constructor 기능을 사용해서 쿼리문을 만들었다.

    Projections.constructor : 필드 직접 접근 방식 (생성자 방식)

    사용할때의 주의점은 생성자의 값을 Expression<?>... exprs 로 넘기기 때문에 생성자와 순서를 꼭 일치시켜야한다.

     

    전체 코드

      @Override
      public Page<CommentResponse> searchComment(CommentPageDto commentPageDto,
          CommentSearchCond commentSearchCond) {
        List<CommentResponse> responses = jpaQueryFactory
            .select(
                Projections.constructor(
                    CommentResponse.class,
                    comment.id,
                    user.nickname,
                    comment.content,
                    comment.createdDate,
                    comment.lastModifiedDate,
                    comment.commentStatus,
                    JPAExpressions
                        .select(commentLike.countDistinct())
                        .from(commentLike)
                        .where(commentLike.commentId.eq(comment.id))
    //                JPAExpressions
    //                    .select(replyComment)
    //                    .from(replyComment)
    //                    .where(replyComment.comment.id.eq(comment.id))
                ))
            .from(comment)
            .leftJoin(user).on(comment.userId.eq(user.id))
            .leftJoin(commentLike).on(commentLike.commentId.eq(comment.id))
            .leftJoin(replyComment).on(replyComment.comment.id.eq(comment.id))
            .where(
                comment.userId.eq(user.id),
                searchByContent(commentSearchCond.getContent()),
                searchByNickname(commentSearchCond.getNickname())
            )
            .groupBy(comment.id)
            .orderBy(orderByCond(commentSearchCond.getCommentSort(),
                    commentSearchCond.getCommentSortDirection()),
                comment.createdDate.desc())
            .offset(commentPageDto.toPageable().getOffset())
            .limit(commentPageDto.toPageable().getPageSize())
            .fetch();
    
        Long count = jpaQueryFactory
            .select(Wildcard.count)
            .from(comment)
            .leftJoin(user).on(comment.userId.eq(user.id))
            .where(
                searchByNickname(commentSearchCond.getNickname()),
                searchByContent(commentSearchCond.getContent())
            )
            .fetch().get(0);
        return new PageImpl<>(responses, commentPageDto.toPageable(), count);
    
      }

    Projections.constructor 을 사용해서 CommentResponse 클래스에 생성자 값을 넣었다.

     

    CommentResponse.java

     // Querydsl 전용
      public CommentResponse(
          Long id,
          String nickname,
          String content,
          LocalDateTime createdAt,
          LocalDateTime lastModifiedAt,
          CommentStatus commentStatus,
          long commentLike
    //      List<ReplyCommentResponse> replyCommentList
      ) {
    
        this.id = id;
        this.nickname = nickname;
        this.content = content;
        this.createdAt = createdAt;
        this.lastModifiedAt = lastModifiedAt;
        this.commentStatus = commentStatus;
        this.commentLike = commentLike;
    //    this.replyCommentList = replyCommentList;
      }

    잘 보면 위에 Projections.constructor를 사용한 부분이랑 순서가 일치한다. 

    JPAExpressions
       .select(commentLike.countDistinct())
       .from(commentLike)
       .where(commentLike.commentId.eq(comment.id))
    // JPAExpressions
    //   .select(replyComment)
    //   .from(replyComment)
    //   .where(replyComment.comment.id.eq(comment.id))

    JPAExpressions (서브쿼리) 를 사용해서 commentLike(좋아요)의 카운트를 가져온다. 주석 친 부분은 대댓글인데.. 오류가 나와 일단 주석처리 해두었다.

    .leftJoin(user).on(comment.userId.eq(user.id))
    .leftJoin(commentLike).on(commentLike.commentId.eq(comment.id))
    .leftJoin(replyComment).on(replyComment.comment.id.eq(comment.id))

    leftJoin을 사용해 댓글의 유저아이디가 유저의 아이디와 같은 대상을 외부조인 시켰고, 

    commentLike(댓글 좋아요)의 commentId가 comment.id (댓글의 아이디)와 같은 대상을 외부조인 시켰다.

    replyComment(대댓글)의 comment.id가 comment.id(댓글의 아이디)와 같은 대상을 외부조인 시켰다.

    where(
    	comment.userId.eq(user.id),
    	searchByContent(commentSearchCond.getContent()),
    	searchByNickname(commentSearchCond.getNickname())
            )
    .groupBy(comment.id)
    .orderBy(orderByCond(commentSearchCond.getCommentSort(),
        commentSearchCond.getCommentSortDirection()),
        comment.createdDate.desc())

    where문에서 댓글의 userId가 user.id(유저의 아이디)와 같은지 확인하고, searchByContent, searchByNickname 메서드를 사용했다.

    private BooleanExpression searchByContent(String content) {
      return Objects.nonNull(content) ? comment.content.contains(content) : null;
    }
    
    private BooleanExpression searchByNickname(String nickname) {
      return Objects.nonNull(nickname) ? user.nickname.contains(nickname) : null;
    }

    둘 다 리턴타입은 BooleanExpression 으로 받게 만들었다.

    searchByContent 메서드가 실행되면 매개변수인 content가 null이 아니면 comment.content.contains에 content가 들어간 후 리턴시키고 null이면 그대로 null 값을 리턴시킨다.

    searchByNickname 메서드도 마찬가지로 실행되면 매개변수인 nickname이 null이 아니면 user.nickname.contains에 nickname이 들어간 후 리턴시키고 null이면 null 값을 리턴시킨다.

     

    groupBy는 comment의 id를 기준으로 만들었고, orderBy는 orderByCond 메서드를 사용해서 만들었다.

    // 아마 검색 기능을 위한 것 같음..
    private CommentSort getCommentSort(CommentSort commentSort) {
      if (Objects.isNull(commentSort)) {
        commentSort = CommentSort.EMPTY;
      } else if (commentSort.equals(CommentSort.COMMENT)) {
        return CommentSort.COMMENT;
      }
      return commentSort;
    }
    
    private CommentSortDirection getcommentSortDirection(CommentSortDirection commentSortDirection) {
      if (Objects.isNull(commentSortDirection)) {
        commentSortDirection = CommentSortDirection.DESC;
      } else if (commentSortDirection.equals(CommentSortDirection.ASC)) {
        return CommentSortDirection.ASC;
      }
      return commentSortDirection;
    }
    
    // 정렬 기능?
    private OrderSpecifier orderByCond(CommentSort commentSort,
        CommentSortDirection commentSortDirection) {
      getCommentSort(commentSort);
      if (getCommentSort(commentSort).equals(CommentSort.EMPTY) && getcommentSortDirection(
          commentSortDirection).equals(CommentSortDirection.ASC)) {
        return comment.createdDate.asc();
      } else if (getCommentSort(commentSort).equals(CommentSort.COMMENT) && getcommentSortDirection(
          commentSortDirection).equals(CommentSortDirection.ASC)) {
        return comment.countDistinct().add(replyComment.countDistinct()).asc();
      } else if (getCommentSort(commentSort).equals(CommentSort.COMMENT) && getcommentSortDirection(
          commentSortDirection).equals(CommentSortDirection.DESC)) {
        return comment.countDistinct().add(replyComment.countDistinct()).desc();
      }
      return comment.createdDate.desc();
    }

    요약하면 정렬 기능을 만들었다. 이 기능을 사용하면 동적 쿼리들을 쉽게 정렬할 수 있다.

    .offset(commentPageDto.toPageable().getOffset())
        .limit(commentPageDto.toPageable().getPageSize())
        .fetch();
    
    Long count = jpaQueryFactory
        .select(Wildcard.count)
        .from(comment)
        .leftJoin(user).on(comment.userId.eq(user.id))
        .where(
            searchByNickname(commentSearchCond.getNickname()),
            searchByContent(commentSearchCond.getContent())
        )
        .fetch().get(0);
    return new PageImpl<>(commentResponses, commentPageDto.toPageable(), count);

    페이징 기능은 offset과 limit가 필요하므로 넣어줬다.

    PageImpl을 리턴시킬 때 count도 넣어줘야해서 count 쿼리를 만들었다. 

     

    SearchMyComment 기능은 위에 코드와 엄청 흡사하므로 패스! (너무 길기도 하고..)

     

    포스트맨으로 실행하면 작동이 잘 된다. 근데 아직 검색기능은 안해봐서 내일 해봐야되겠다.

    대댓글 기능도 고칠려고 했는데 생각해보면 댓글로 검색하면 댓글만 나오지 대댓글은 나오지 않는다.

    검색 된 게시글이나 댓글을 눌러야지 대댓글이 출력된다. 그래서 일단 고치지는 않았다.

    내일 팀원분께 한번 말해봐서 어떻게 해야할지 정해야겠다.

    아직 김영한님의 Querydsl 강의를 다 듣지 못했는데 벌써 하자니 머리가 정말 아팠다 ㅋㅋㅋ..

     

     

     

Designed by Tistory.