미니 프로젝트/게시판

[Java] 게시판 ④ Repository(search)

웹개발자(진) 2024. 5. 22. 11:09
반응형

다형성 예시

운송 수단 클래스와 하위 클래스들:

  • 상위 클래스: 운송 수단(Vehicle)
  • 하위 클래스: 자동차(Car), 자전거(Bicycle), 비행기(Airplane)

운송 수단(Vehicle) 클래스는 이동하기(move)라는 메서드를 갖고 있습니다. 이 메서드는 자동차, 자전거, 비행기 등에서 다르게 구현됩니다. 자동차는 바퀴를 굴려 이동하고, 자전거는 페달을 밟아 이동하며, 비행기는 날개를 이용해 날아갑니다. 운송 수단 타입의 변수로 자동차, 자전거, 비행기 객체를 모두 참조할 수 있고, 이동하기 메서드를 호출하면 해당 객체에 맞는 방식으로 이동합니다.

이 다형성의 이점을 이용하려고 Repository를 만듭니다


1. BoardSearch(interface)

package org.zerock.b01.repository.search;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.zerock.b01.domain.Board;

public interface BoardSearch {
    Page<Board> searchAll(String[] types, String keyword, Pageable pageable);
}

BoardSearch를 인터페이스로 두는 이유는 설계의 유연성을 높이고, 구현과 인터페이스를 분리하여 코드의 유지보수성을 향상하기 위함입니다. 또한, 다형성을 제공하고, 테스트 용이성을 높이는 데 기여합니다. 이러한 접근 방식은 복잡한 쿼리를 다루는 Spring Data JPA 프로젝트에서 특히 유용합니다. 

searchAll : 제목, 내용 키워드들의 값을 비교하여 해당값을 가져오는 query문을 작성해야 합니다. JpaRepository안에도 간단한 CRUD query문을 만들 수 있지만,

일반적인 query문으로는 복잡한 명령에 대해서 답하기 어렵습니다. 해당 내용은 인터페이스에서는 변수지정만 해주고 Impl에서 상속받아서 정의해 줍니다. extends 통해 QuerydslRepositroySupport를 상속받는데 다양한 커스텀 쿼리 메서드들을 만들 수 있습니다.


 

2. BoardSearchImpl(search)

아래의 코드는 Querydsl을 사용해서 데이터를 검색하는 기능을 구현한 코드입니다.

package org.zerock.b01.repository.search;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.JPQLQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.zerock.b01.domain.Board;
import org.zerock.b01.domain.QBoard;

import java.util.List;

public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch{
    public BoardSearchImpl(){
        super(Board.class);
    }

    @Override
    public Page<Board> searchAll(String[] types, String keyword, Pageable pageable){
        QBoard board = QBoard.board;
        JPQLQuery<Board> query = from(board);

        if((types != null && types.length>0) && keyword != null){
            BooleanBuilder booleanBuilder = new BooleanBuilder();
            for(String type: types){
                switch (type) {
                    case "t":
                        booleanBuilder.or(board.title.contains(keyword));
                        break;
                    case "c":
                        booleanBuilder.or(board.content.contains(keyword));
                        break;
                    case "w":
                        booleanBuilder.or(board.writer.contains(keyword));
                        break;
                }
            }
            query.where(booleanBuilder);
        }
        query.where(board.bno.gt(0L));

        //paging
        this.getQuerydsl().applyPagination(pageable, query);
        List<Board> list = query.fetch();
        Long count = query.fetchCount();
        return new PageImpl<>(list, pageable, count);
    }
}

QuerydslRepositorySupport는 Querydsl 프레임워크에서 제공하는 클래스로, Querydsl 쿼리를 Spring Data 레포지토리와 통합하는 데 도움을 줍니다. 이 클래스는 JPA(Java Persistence API) 엔터티에 대한 타입 안전한 쿼리를 작성하는 데 사용되는 Querydsl JPA 모듈의 일부입니다.

 
  1. BoardSearchImpl 클래스: 이 클래스는 QuerydslRepositorySupport를 확장하여 게시판을 검색하는 기능을 구현합니다.
  2. 생성자: BoardSearchImpl 클래스의 생성자에서는 부모 클래스인 QuerydslRepositorySupport의 생성자를 호출하여 해당 클래스가 작업할 엔티티 유형을 지정합니다. 여기서는 Board 엔티티가 작업 대상입니다.
  3. searchAll 메서드: 이 메서드는 게시판을 검색하고 페이지별로 결과를 반환합니다. 메서드는 검색 조건에 따라 적절한 Querydsl 쿼리를 생성하고 실행합니다.
  4. 검색 조건: 메서드는 검색할 타입(types)과 키워드(keyword)를 매개변수로 받습니다. 검색할 타입은 제목(title), 내용(content), 작성자(writer) 중 하나를 선택할 수 있습니다. 선택한 타입에 따라 Querydsl의 BooleanBuilder를 사용하여 적절한 검색 조건을 추가합니다.
  5. 페이징: 메서드는 Spring Data의 Pageable 객체를 사용하여 결과를 페이지별로 가져옵니다. applyPagination 메서드를 사용하여 쿼리에 페이징 정보를 적용하고, fetch 메서드를 사용하여 해당 페이지의 결과를 가져옵니다. 또한 전체 결과 수를 가져오기 위해 fetchCount 메서드를 사용합니다.

 

검색조건에 대한 Querydsl 생성하는 코드입니다. JPQLQuery에서 Board entity가 작업대상입니다. from(board)가 일반적으로 query문에 들어가는 from을 생각하면 연상하기 좋습니다.

 @Override
    public Page<Board> searchAll(String[] types, String keyword, Pageable pageable){
        QBoard board = QBoard.board;
        JPQLQuery<Board> query = from(board);

어떤 조건을 걸어야 하는가에 대해 복잡한 조건을 걸고 싶은데 코드는 메서드는 한 적정입니다. 그럴 때 booleanBuilder를 사용하게 됩니다.

BooleanBuilder는 Java에서 널리 사용되는 쿼리 빌더 라이브러리인 QueryDSL에서 제공하는 클래스입니다. 이 클래스는 동적 쿼리를 생성하는 데 도움을 줍니다. BooleanBuilder를 사용하면 조건에 따라 SQL WHERE 절을 동적으로 생성할 수 있습니다. 

        if((types != null && types.length>0) && keyword != null){
            BooleanBuilder booleanBuilder = new BooleanBuilder();
            for(String type: types){
                switch (type) {
                    case "t":
                        booleanBuilder.or(board.title.contains(keyword));
                        break;
                    case "c":
                        booleanBuilder.or(board.content.contains(keyword));
                        break;
                    case "w":
                        booleanBuilder.or(board.writer.contains(keyword));
                        break;
                }
            }
            query.where(booleanBuilder);
        }
        query.where(board.bno.gt(0L));

BooleanBuilder 객체를 생성해서 해당 객체 안에 조건들을 넣어줍니다. Null 값 또는 아무 값도 입력하지 않았을 경우를 대비 if문을 통해 어떠한 값이 들어왔을 때 조건을 실행하도록 합니다. types안에는 나중에 해당 조건에 맞는 String 값이 들어오게 될 것입니다. 아래는 선택에 대한 View 예시입니다.

<option value="t" th:selected="${pageRequestDTO.type =='t'}">제목</option>
<option value="c" th:selected="${pageRequestDTO.type =='c'}">내용</option>
<option value="w" th:selected="${pageRequestDTO.type =='w'}">작성자</option>
<option value="tc" th:selected="${pageRequestDTO.type =='tc'}">제목 내용</option>
<option value="tcw" th:selected="${pageRequestDTO.type =='tcw'}">제목 내용 작성자</option>

switch문을 통해서 해당 조건에 맞는 case를 찾아가고 엔티티에 있는 값에 입력한 keyword의 값이 포함되는지 확인하는 코드를 작성하고 이를 query문에 where절로 지정해 줍니다. 

QueryDsl 같은 경우 조건표현을 위해 다양한 메서드를 가지고 있는데 그중 대표적인 메서드 몇 개만 설명드리겠습니다.

  • gt (greater than): 초과 조건을 나타냅니다.
board.bno.gt(0L); // bno가 0보다 큰
  • lt (less than): 미만 조건을 나타냅니다.
board.bno.lt(10L); // bno가 10보다 작은
  • eq (equal): 동등 조건을 나타냅니다.
board.title.eq("example"); // title이 "example"인
  • startsWith: 특정 문자열로 시작하는 조건을 나타냅니다.
board.title.startsWith("example"); // title이 "example"로 시작하는
 

 


 

this.getQuerydsl().applyPagination(pageable, query);
List<Board> list = query.fetch();
Long count = query.fetchCount();
return new PageImpl<>(list, pageable, count);

 applyPagination 메서드는 QuerydslRepositroySupport클래스에 제공하는 메서드입니다. Pageable객체를 사용해서 쿼리에 Pagination 처리를 적용해 줍니다. 아까 위에서 만들었던 query문을 pageable에 얹는다고 생각하면 좋을 거 같습니다.

query.fetch()를 통해 위 조건으로 만든 query문을 실행시켜 list형식으로 값을 받아옵니다.

해당 내용들을 PageImpl <>에 담아서 return 해줍니다.

 

페이징?

PageRequest.of(int page, int size)
ex) Pageable pageable = PageRequest.of(0, 10);

PageRequest.of()는 Spring Data에서 페이징 된 데이터를 가져오기 위해 사용되는 정적 팩토리 메서드입니다. 이 메서드는 페이징 처리에 필요한 정보를 제공하여 특정 페이지를 요청하고 해당 페이지의 크기를 지정합니다.

  • page: 요청할 페이지의 번호를 나타냅니다. 페이지 번호는 0부터 시작합니다.
  • size: 한 페이지당 가져올 항목의 개수를 나타냅니다.

게시판 코드에서는 PageRequestDTO에서 생성자를 통해서 Pageable을 생성합니다.

PageRequest.of(this.page - 1, this.size, Sort.by(props).descending()

 page값을 1로 default값을 주었었는데 index기준으로 0이 첫 번째 이기 때문에 -1을 해주고 사이즈도 default값으로 10을 주었습니다. 예시에 없는 세 번째 매개변수는 정렬방법을 매개변수로 받습니다. 지금은 입력받은 값을 기준으로 descending() 내림차순 하고 있습니다.

이러한 방식으로 BoardSearchImpl 클래스는 Querydsl을 사용하여 게시판을 검색하고 페이지별로 결과를 반환합니다.

반응형