-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Labels
Description
🔄 리팩토링 대상
[검색 도메인]
: Board.sevice/ getallBoards, getBoardByCategory
- 태그에 대한 검색 요청이 없을때도 불필요한 조인이 발생
- 페이지 전체 수를 반환을 위해 쿼리가 한번 더 발생하는데 이부분에 대한 확인이 필요
- 검색기능에서 내용이 저는똑똑한개발자문선민입니다 라는 내용에서 똑똑한문선민이라고 검색하면 찾지못함
// 전체 게시물 조회
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
Reactions are currently unavailable