JAVAIARY

JPQL로 검색하기 본문

Project/2023.02~ ) Study toy 프로젝트

JPQL로 검색하기

shiherlis 2023. 3. 13. 00:16

1. Repository의 확장

  • 쿼리메서드나 @Query 등으로 처리할 수 없는 기능은 별도의 인터페이스로 설계
  • 별도의 인터페이스에 대한 구현 클래스를 작성합니다.
    이 때 QuerydslRepositorySupport라는 클래스를 부모 클래스로 사용
  • 구현 클래스에 인터페이스의 기능을 Q도메인 클래스와 JPQLQuery를 이용해서 구현

1) compileJava 실행

Gradle-other-compilJava 실행

  • Q도메인 클래스 생성 확인

2) search 패키지와 Repository 생성 

 

public interface SearchBoardRepository {
    Board search1();
}
@Log4j2
public class SearchBoardRepositoryImpl extends QuerydslRepositorySupport implements SearchBoardRepository {

    public SearchBoardRepositoryImpl(){
        super(Board.class);
    }
    @Override
    public Board search1() {
        
        log.info("search1................");
        
        return null;
    }
}
  • QuerydslRepositorySupport 클래스를 상속해야함
  • QuerydslRepositorySupport는 생성자가 존재하므로 클래스 내에서 super()를 이용하여 호출해야 함
    💡 상속받는 자식 클래스의 인스턴스 생성시, 부모 클래스의 기본 생성자를 자동으로 호출 하게 됨.
      그러나 매개변수가 있는 부모 클래스의 생성자는 자동으로 호출되지 않는다.
      그러므로 super( ) 를 통해 부모 클래스 생성자를 호출하고 부모클래스의 생성자가 요구하는 매개변수를 넣어줌
      * super( ) 는 반드시 자식 생성자의 첫 줄에 위치해야 함

QuerydslRepositorySupport 클래스의 생성자; 파라미터로 도메인클래스를 받음

  • 도메인 클래스를 지정하는데 null 값을 넣을 수 없으므로 Board.class를 넣어줌

  • BoardRepository 에서 SearchBoardRepository 를 상속하는 형태로 변경

2) test

@Test
public void testSearch1(){
    boardRepository.search1();
}

log.info에서 동작 확인


2. JPQL Query객체

  • Querydsl 라이브러리 내에 존재하는 JPQLQuery 인터페이스 활용

1) SearchBoardRepositoryImpl

@Override
public Board search1() {
    log.info("search1................");

    QBoard board = QBoard.board;
    JPQLQuery<Board> jpqlQuery = from(board);
    
    jpqlQuery.select(board).where(board.bno.eq(1L));
    
    log.info("----------------------------");
    log.info(jpqlQuery);
    log.info("----------------------------");

    List<Board> result = jpqlQuery.fetch();
    
    return null;
}

  • 실제 SQL문 실행

3. JPQLQuery의 leftJoin() / on()

  • Board와 Reply Left (outer) join
@Override
public Board search1() {
    log.info("search1......................................");
    QBoard board = QBoard.board;
    QReply reply = QReply.reply;
    JPQLQuery<Board> jpqlQuery = from(board);
    jpqlQuery.select(board).where(board.bno.eq(1L));
    jpqlQuery.leftJoin(reply).on(reply.board.eq(board));

    log.info("------------------------");
    log.info(jpqlQuery);
    log.info("------------------------");

    List<Board> result = jpqlQuery.fetch();

    return null;
}


4. Tuple 객체

  • JPQLQUery의 leftJoin(), join()을 이용해서 Board, Member, Reply를 처리할 수 있고,
    groupBy() 등을 이용해서 집계함수 처리 가능
  • 정해진 엔티티 객체 단위가 아니라 각각의 데이터를 추출하는 경우에는 Tuple 객체를 사용
  • Tuple은 JPQLQuery에서 데이터를 추출(projection)하는 용도로 제한적으로 사용
@Override
public Board search1(){
    log.info("search1......................................");
    QBoard board = QBoard.board;
    QReply reply = QReply.reply;
    QMember member = QMember.member;

    JPQLQuery<Board> jpqlQuery = from(board);
    jpqlQuery.leftJoin(member).on(board.writer.eq(member));
    jpqlQuery.leftJoin(reply).on(reply.board.eq(board));

    JPQLQuery<Tuple> tuple = jpqlQuery.select(board, member.email, reply.count()).groupBy(board);

    log.info("------------------------");
    log.info(jpqlQuery);
    log.info("------------------------");

    List<Board> result = jpqlQuery.fetch();

    return null;
}

-JPQL

select board, member1.email, count(reply)
from Board board
  left join Member member1 with board.writer = member1
  left join Reply reply with reply.board = board
group by board, member1

-SQL

  • Board 객체, 작성자 이메일, 댓글 개수 출력

5. JPQLQuery로 Page<Object[]> 처리

1) SearchPage 생성 및 테스트

searchPage 메서드 추가

  • 가능하면 DTO를 Repository 영역에서 다루지 않도록 하기 위해
    검색 타입(type), 키워드 (keyword), 페이지 정보 (Pageable)를 파라미터로 전달
@Override
public Page<Object[]> searchPage(String type, String keyword, Pageable pageable) {
    log.info("searchPage..............................");
    return null;
}
  • SearchBoardRepositoryImpl 에서 구현

Test 추가

@Test
public void testSearchPage(){
    Pageable pageable = PageRequest.of(0,10, Sort.by("bno").descending());

    Page<Object[]> result = boardRepository.searchPage("t", "1", pageable);

}

실행확인


2) SearchPage에 검색 조건 추가

@Override
public Page<Object[]> searchPage(String type, String keyword, Pageable pageable) {
    log.info("searchPage..............................");

    QBoard board = QBoard.board;
    QReply reply = QReply.reply;
    QMember member = QMember.member;

    JPQLQuery<Board> jpqlQuery = from(board);
    jpqlQuery.leftJoin(member).on(board.writer.eq(member));
    jpqlQuery.leftJoin(reply).on(reply.board.eq(board));

    //Select b, w, count(r) From Board b
    //Left Join b.writer w Left Join Reply r On r.board= b
    JPQLQuery<Tuple> tuple = jpqlQuery.select(board,member,reply.count());

    BooleanBuilder booleanBuilder = new BooleanBuilder();
    BooleanExpression expression = board.bno.gt(0L);

    booleanBuilder.and(expression);

    if(type != null){
        String[] typeArr = type.split("");
        //검색 조건 작성하기
        BooleanBuilder conditionBuilder = new BooleanBuilder();

        for(String t:typeArr){
            switch (t){
                case "t":
                    conditionBuilder.or(board.title.contains(keyword));
                    break;
                case "w":
                    conditionBuilder.or(member.email.contains(keyword));
                    break;
                case "c":
                    conditionBuilder.or(board.content.contains(keyword));
                    break;
            }
        }
        booleanBuilder.and(conditionBuilder);
    }
    tuple.where(booleanBuilder);
    tuple.groupBy(board);
    List<Tuple> result = tuple.fetch();
    log.info(result);
    
    return null;
}
  • Tuple 에 기본 조회 속성 및 조인처리를 해 준 뒤,

  • BooleanBuilder를 통해 Where 절을 검색 조건에 맞게 작성해 준다.
  • 검색조건이 변경되면 where절도 함께 변경됨

  • tuple에 where 절 추가 및 groupBy 지정
  • fetch() : List 형식으로 변환하여

- 미리 작성해 놓은 Test 코드를 통해 확인

3) sort / count 처리

  • JPQL에서는 Sort 객체를 지원하지 않음
  • OrderSpecifier<Textends Comparable> 를 파라미터로 처리 필요
//order by
Sort sort = pageable.getSort();

//tuple.orderBy(board.bno.desc());
sort.stream().forEach(order -> {
    Order direction = order.isAscending()? Order.ASC : Order.DESC;
    String prop = order.getProperty();

    PathBuilder orderByExpression = new PathBuilder(Board.class, "board");

    tuple.orderBy(new OrderSpecifier(direction, orderByExpression.get(prop)));

});
tuple.groupBy(board, member);

//page 처리
tuple.offset(pageable.getOffset());
tuple.limit(pageable.getPageSize());

List<Tuple> result = tuple.fetch();

log.info(result);

long count = tuple.fetchCount();

log.info("COUNT:" + count);

return new PageImpl<Object[]>(result.stream().
        map(t -> t.toArray()).collect(Collectors.toList()), pageable, count);
  • searchPage()의 where절 이하 변경

  • and() 를 이용하여 고의적으로 중첩되는 정렬 조건 추가 

추가된 정렬 조건 (order by절)
Object[]&nbsp; 리스트로 처리된 모습
추가 카운트 sql 실행 후 카운트 출력

 


6. 목록화면에서 검색 처리

1) BoardServiceImpl 클래스 getList 수정

    @Override
    public PageResultDTO<BoardDTO, Object[]> getList(PageRequestDTO pageRequestDTO) {

        log.info(pageRequestDTO);

        Function<Object[], BoardDTO> fn = (en -> entityToDTO((Board)en[0], (Member)en[1], (Long)en[2]));

//        Page<Object[]> result = repository.getBoardWithReplyCount(pageRequestDTO
//                .getPageable(Sort.by("bno").descending()));
        Page<Object[]> result = repository.searchPage(pageRequestDTO.getType(),
                pageRequestDTO.getKeyword(), pageRequestDTO.getPageable(Sort.by("bno").descending()));

        return new PageResultDTO<>(result, fn);
    }
  • 타입, 키워드, 정렬조건을 받도록 변경

  • 검색 키워드를 넣고 정상적으로 검색 결과가 출력되는 것 확인~