본문 바로가기
Sparta 내일배움캠프 Java 5기/[최종 프로젝트] 커뮤니티 서비스 Team Project ✓ 2024. 7. 24.

(24.07.24) 코드 리뷰 게시글 조회 & 카테고리 검색 기능 구현

코드 리뷰 CodeReview 조회 & 카테고리 검색 기능 - JPQL 활용 기능

  • 팀원들의 QueryDSL 사용에 있어서 불편함이 있다는 의견이 많아 JPQL과 JpaRepository 상속받아서 쿼리 메서드를 사용하는 방향으로 결정
    • 본인은 SQLD 공부를 했었기 때문에 조회 관련하여 SQL다루는 것이 어렵지 않아 크게 문제가 되지 않으나, QueryDSL을 사용하기 위해서는 JPAQueryFactory 의 Bean등록을 위한 Configuration 클래스와, QueryDSL을 사용할 수 있는 인터페이스와 해당 구현을 해야하는 Repository  클래스를 더 추가해야하므로 볼륨이 굉장히 커진다고 판단

코드 리뷰 CodeReviews 전체 게시글 조회 기능

 // 게시글 전체 조회
 public CodeReviewsListResponseDto getAllCodieReviews(int page) {

    Pageable pageable = PageRequest.of(page, SizingConstants.PAGE_SIZE);

    Page<Tuple> codeReviewsPage = codeReviewsRepository.findAllWhereStatusIsActiveOrderByCreatedAtDesc(
        pageable);

...

    return new CodeReviewsListResponseDto(...);
  }
 
  • 페이지네이션을 위해서 Page객체를 만들어서 조회를 할 수 있게끔 설계
    • React 화면단 구성에서 자리가 부족하거나 페이지당 조회해 오는 게시글 수에 의한 오류가 발생할 수 있다는 가능성이 있으므로 SizingConstants클래스를 만들어서 상수화 시켜서 한번에 수정할 수 있게 설정
// 전체조회 JPQL을 보기 쉽게 줄을 나눈 형태
@Query(
    "SELECT cr, u.name " +
    "FROM CodeReviews cr " +
    "LEFT JOIN Users u ON cr.user.id = u.id " +
    "WHERE cr.status = 'ACTIVE' " +
    "ORDER BY cr.createdAt DESC"
)
Page<Tuple> findAllWhereStatusIsActiveOrderByCreatedAtDesc(Pageable pageable);
  • SQL과 다를바 없이 그대로 사용할 수 있는 JPQL형태를 사용
  • CodeReviews 게시글 엔터티는 User와 N:1 관계를 가지고 있지만, 이는 user_id 로 연관관계가 설정이 되어있기때문에, 화면단에서 응답을 해주기 위해서 user 의 이름 name 필드 값이 필요
    • CodeReviews를 다 가지고 와서 하나하나 Stream 또는 for 문을 통해서 하나씩 엔터티의 user_id를 하나씩 불러와서 Dto에 함께 설정해서 하는 방식은 코드도 길어지고, 직관적이지 못함 + 성능에 있어서 무리가 갈 수 있다고 판단 -> JPQL @Query 를 통해서 LEFT JOIN을 통한 테이블에서 필요한 컬럼만 가지고 와서  Tuple 객체로 가지고 온뒤  DTO 생성자를 통해서 Response DTO 생성 후 응답
// User 이름과 같이 CodeReviews 객체에 대한 정보를 응답시키기 위해 사용하는 ResponseDTO 객체
public class CodeReviewsWithUserResponseDto {
...

  public CodeReviewsWithUserResponseDto(Tuple tuple) {
    CodeReviews codeReviews = tuple.get(0, CodeReviews.class);
    String name = tuple.get(1, String.class);

    this.id = codeReviews.getId();
    this.title = codeReviews.getTitle();
    this.name = name;
    this.category = codeReviews.getCategory();
    this.contents = codeReviews.getContents();
    this.code = codeReviews.getCode();
    this.createdAt = codeReviews.getCreatedAt();
    this.modifiedAt = codeReviews.getModifiedAt();
  }
}
  • 추후 User의 다른 부분만 가지고 오고 싶다면, Tuple 객체로 받아오기 때문에 따로 객체 변경 없이 필드만 수정만 하면 될 수 있도록 설정

코드 리뷰 CodeReviews 특정 게시글 조회 기능

  • 단건 게시물의 조회를 할경우 해당 코드리뷰 게시글 + 게시글의 댓글 + 댓글마다의 좋아요 수 정보를 한꺼번에 가져와야하기 때문에 비지니스 로직에선 구현하거나 get으로 불러올 경우 N+1 이슈가 반드시 발생할 가능성이 매우 높음 -> JPQL상에서 게시글,댓글,좋아요 테이블 3개를 한번에 조인해서 필요한 필드만 가지고오는 위의 방식을 그대로 채용
public CodeReviewsDetailResponseDto getCodeReview(Long codeReviewsId) {
...
    List<CodeReviewCommentsWithLikesResponseDto> commentList = codeReviewCommentsRepository.findByCodeReviewIdWithDetails(
            codeReviewsId).stream()
        .map(CodeReviewCommentsWithLikesResponseDto::new)
        .collect(Collectors.toList());

    return new CodeReviewsDetailResponseDto(... ,
        commentList);
  }
// 단건조회 JPQL을 보기 쉽게 줄을 나눈 형태
@Query(
    "SELECT crc, u.name, COUNT(l) AS likes " +
    "FROM CodeReviewComments crc " +
    "LEFT JOIN Users u ON crc.user.id = u.id " +
    "LEFT JOIN Likes l ON crc.id = l.codeReviewComment.id AND l.status = 'LIKED' " +
    "WHERE crc.codeReviews.id = :codeReviewsId " +
    "AND crc.status = 'ACTIVE' " +
    "GROUP BY crc.id, u.name " +
    "ORDER BY crc.createdAt DESC"
)
List<Tuple> findByCodeReviewIdWithDetails(Long codeReviewsId);
  • 작성하는데 기본적인 JOIN문을 사용해서 쓴다면 길어보여도 쉬운 SQL문
    • 좋아요 + 댓글  조인일 경우 좋아요와 댓글 아이디가 일치하고, LIKED 상태인 객체만 JOIN할 수 있도록 ON 조건절
    • 사용자 + 댓글 조인일 경우 단순하게 댓글 아이디가 일치한 경우 조인
    • 3개 테이블이 JOIN 되었을 때, SELECT 문으로 Likes 객체의 ROW 수만 COUNT 해주면서 좋아요 수를 조회
  • 이것 역시 Tuple 객체로 가지고 와서 ResponseDTO로 바꿔서 바로 적용할 수 있도록 진행

코드 리뷰 CodeReviews 카테고리 검색 기능

  • #해시태그 형태로 사용자는 CodeReview 게시글 객체에 Category를 자유롭게 적을 수 있음
    • Service 단에서 Arrage해주는 메서드를 통새서 소문자+빈칸 없애기 를통해서 #java #security #인증 #인가 처럼 한줄의 String 형태로 DB에 저장이 되어 있는 형태
    • 따라서 카테고리를 검색창에 입력해서 요청할 경우, "#(검색어) " 가 Category에 포함된 CodeReviews 데이터에서 가지고 와서 전체 조회 처럼 Pagenation진행후 조회하는 방식으로 구상
public CodeReviewsSearchResponseDto getCodeReviewsByCategory(String category, int page) {

    Pageable pageable = PageRequest.of(page, SizingConstants.PAGE_SIZE);

...
	category = arrangeCategory(category); // 앞에 # 넣기

    Page<Tuple> codeReviewsPage = codeReviewsRepository.findAllByCategory(category + " ", pageable);

    List<CodeReviewsWithUserResponseDto> responseDtoList = codeReviewsPage.getContent().stream()
        .map(CodeReviewsWithUserResponseDto::new)
        .collect(Collectors.toList());

    return new CodeReviewsSearchResponseDto(
        ...
        responseDtoList);
  }
@Query(
    "SELECT cr, u.name " +
    "FROM CodeReviews cr " +
    "LEFT JOIN Users u ON cr.user.id = u.id " +
    "WHERE cr.status = 'ACTIVE' " +
    "AND cr.category LIKE CONCAT('%', :category, '%') " +
    "ORDER BY cr.createdAt DESC"
)
Page<Tuple> findAllByCategory(String category, Pageable pageable);
  • 전체조회 JPQL의 쿼리와 똑같지만, AND cr.category LIKE CONCAT('%', :category, '%') 의 조건을 하나 더 넣어줘서 간편하게 검색 기능을 구현할 수 있었음
  • 단, RequestParam으로 받아온 검색어는 검색어 앞에는 # 뒤에 빈칸을 넣어주어서 java를 검색했는데 #javascript가 태카테고리로 포함된 정보가 검색이 되지 않도록 함