Skip to content

Refactor: 검색기능 최적화 #14

@moonsunmean

Description

@moonsunmean

🔄 리팩토링 대상

[검색 도메인]
: Board.sevice/ getallBoards, getBoardByCategory

  1. 태그에 대한 검색 요청이 없을때도 불필요한 조인이 발생
  2. 페이지 전체 수를 반환을 위해 쿼리가 한번 더 발생하는데 이부분에 대한 확인이 필요
  3. 검색기능에서 내용이 저는똑똑한개발자문선민입니다 라는 내용에서 똑똑한문선민이라고 검색하면 찾지못함
// 전체 게시물 조회
    public Page<BoardResponseDto> getAllBoard(Pageable pageable, List<String> tagNames, String contents) {
        QBoard board = QBoard.board;
        QTagBoard tagBoard = QTagBoard.tagBoard;

        BooleanBuilder builder = buildDynamicQuery(tagNames, contents, null);

        return fetchBoards(pageable, builder);
    }

    // 카테고리별 게시물 조회
    public Page<BoardResponseDto> getBoardByCategory(Pageable pageable, List<String> tagNames,
                                                        String contents,Long categoryId ) {
        QBoard board = QBoard.board;
        QTagBoard tagBoard = QTagBoard.tagBoard;
        BooleanBuilder builder = buildDynamicQuery(tagNames, contents, categoryId);

        return fetchBoards(pageable, builder);
    }
private BooleanBuilder buildDynamicQuery(List<String> tagNames, String contents, Long categoryId) {
        QBoard board = QBoard.board;
        QTagBoard tagBoard = QTagBoard.tagBoard;
        QTag tag = QTag.tag;
        BooleanBuilder builder = new BooleanBuilder();

        // 카테고리 조건
        if (categoryId != null) {
            builder.and(board.category.id.eq(categoryId)); //카테고리 ID와 같은 ID값만 추출
        }

        // 태그 조건 : 각 태그를 모두(AND)를 포함한 게시글
        // select board_id from tag_board
        //where tag_id in(select id from tag where name in("a","b"))
        //GROUP BY board_id HAVING COUNT(tag_id) = {입력받은 태그 사이즈};  ->  태그 개수 확인

        // 태그이름 리스트 -> ID변환
        if (tagNames != null && !tagNames.isEmpty()) {
            // 태그 이름을 기반으로 태그 ID 조회
            List<Long> tagIds = queryFactory
                    .select(tag.id)
                    .from(tag)
                    .where(tag.name.in(tagNames))
                    .fetch();


            // 태그가 모두 포함된 게시글 필터링
            builder.and(board.id.in(
                    queryFactory
                            .select(tagBoard.board.id)
                            .from(tagBoard)
                            .where(tagBoard.tag.id.in(tagIds))
                            .groupBy(tagBoard.board.id)
                            .having(tagBoard.tag.id.count().eq((long) tagNames.size()))
                            .fetch()
            ));
        }

        // 검색어(게시글 내용) 조건 : 해당 내용이 포함된 게시글(대소문자 구분X)
        // select board_id from board
        // where Lower(board.content) like concat('%',LOWER(:contents,'%')
        if (contents != null && !contents.isEmpty()) {
            builder.and(board.content.containsIgnoreCase(contents)); // = Like %LOWER{contents}%
        }

        return builder;
    }
// 빌더 적용 패치함수
  private PageImpl<BoardResponseDto> fetchBoards(Pageable pageable, BooleanBuilder builder) {
      QBoard board = QBoard.board;
      QTagBoard tagBoard = QTagBoard.tagBoard;

      List<Board> boards = queryFactory
              .selectDistinct(board)
              .from(board)
              .leftJoin(board.tagBoards, tagBoard)
              .where(builder)
              .offset(pageable.getOffset())
              .limit(pageable.getPageSize())
              .fetch();

      long total = queryFactory
              .selectDistinct(board)
              .from(board)
              .leftJoin(board.tagBoards, tagBoard)
              .where(builder)
              .fetchCount();

      List<BoardResponseDto> content = boards.stream()
              .map(BoardResponseDto::fromEntity)
              .collect(Collectors.toList());

      return new PageImpl<>(content, pageable, total);
  }

🎯 리팩토링 목표

[검색성능 최적화]
검색 성능을 높인다. (조회 로직 속도 향상, 필요없는 쿼리 요청 삭제, 검색 필터링 고도화)

[Hateoas 적용]
Hateoassms 서버가 클라이언트에게 하이퍼미디어(링크를 통해 공유된 자원)를 통해 정보를 동적으로 제공해주는 방법이다.

PAGEMODE
Pageable 자료구조의 응답 표현을 dto에 바인딩하기 위해 필요한 객체를 이고
내부에서 페이지의총 갯수를 반환함

 long total = countQuery
                .where(builder)
                .fetchCount();

해당 기능은 기존 코드와 성능 차이는 작은서비스에선 크게나지 않음 -> 차라리 HATEAOS의 장점을 가져가고 이를 통해 REST 규칙을 지키며 개발하기!

🛠 작업 내용

  • 1. 조회 속도 향상
  • 2. 조회 정확도 향상

📎 참고 자료

https://www.youtube.com/watch?v=RP_f5dMoHFc (PageModel 부분 참고) -- 그런 REST API로 괜찮은가?
https://docs.spring.io/spring-hateoas/docs/current/api/org/springframework/hateoas/PagedModel.html - PagedModel (Spring HATEOAS 1.3.3 API)
https://esbook.kimjmin.net/ - Elastic Search

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions