관리 메뉴

bright jazz music

guestbook : 11. 검색처리(1) 본문

Framework/Spring

guestbook : 11. 검색처리(1)

bright jazz music 2022. 7. 22. 21:57

● 검색 처리는 크게 두 가지로 나눌 수 있다.

  • 서버 사이드 처리
  • 화면 사이드 처리

검색 항목은 대략 아래와 같이 정의한다.

 

  • '제목(t), 내용(c), 작성자(w)'로 검색하는 경우
  • '제목(t) 혹은 내용(c)'으로 검색하는 경우
  • '제목(t) 혹은 내용(c) 혹은 작성자(w)'로 검색하는 경우

 

1. 서버 사이드 처리

  • PageRequestDTO에 검색타입(type)과 키워드(keyword) 추가
  • 이하 서비스 계층에서 Querydsl을 이용해서 처리

1-1. PageRequestDTO에 검색조건(type)과 검색 키워드(keyword) 추가

//PageRequestDTO.java

package com.example.guestbook.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; //페이지 관련 임포트
import org.springframework.data.domain.Sort;

@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO {


    private int page;
    private int size;

    //검색을 위해 추가
    private String type;
    private String keyword;

    public PageRequestDTO(){
        this.page = 1;
        this.size = 10;
    }

    //  이 DTO의 목적은 JPA에서 사용하는 Pageable 타입의 객체를 생성하는 것이다.
    //  JPA를 이용하는 경우에는 페이지 번호가 0부터 시작한다.
    //  따라서 1페이지의 경우 0이 될 수 있도록 아래와 같이 작성한다.
    //  정렬(Sort)는 다양한 상황에서 사용하기 위해서 별도의 파라미터로 받도록 설계.
    public Pageable getPageable(Sort sort) {
        return PageRequest.of(page - 1, size, sort );
    }
}

type과 keyword 속성이 클래스에 추가되었다.

 

 

1-2. 서비스 계층의 검색 구현과 테스트

 

동적으로 검색 조건이 처리되는 경우의 실제 코딩은 아래와 같다.

  • Querydsl을 통해 BooleanBuilder 작성
  • GuestbookRepository는 Querydsl로 작성된 BooleanBuilder를 findAll()을 처리하는 용도로 사용

BooleanBuilder 작성은 별도의 클래스 등을 작성해서 처리할 수 있다.

그러나 간단히 하려면 GuestbookServiceImple 내에 메서드를 하나 작성해서 처리해도 된다.

 

//GuestbookImpl.java

package com.example.guestbook.service;

import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import com.example.guestbook.entity.QGuestbook;
import com.example.guestbook.repository.GuestbookRepository;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.function.Function;

@Service //스프링이 빈으로 처리하도록 @Service 어노테이션 추가
@Log4j2
@RequiredArgsConstructor // <== 의존성 자동 주입!
public class GuestbookServiceImpl implements GuestbookService {

    private final GuestbookRepository repository; //jpa 처리를 위해 repository 주입! 반드시 fianl 사용

    @Override //GuestbookService에서 상속한 메서드 오버라이딩
    public Long register(GuestbookDTO dto) {

        log.info("DTO--------------------------------------");
        log.info(dto);

        Guestbook entity = dtoToEntity(dto); // GuestbookService에서 default를 이용하여 생성한 메서드

        log.info(entity);

        repository.save(entity); // 처리 저장.
//        return null;
        return entity.getGno(); //처리 후에는 엔티티의 gno 리턴
    }

    @Override
    public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestedDTO){
        Pageable pageable = requestedDTO.getPageable(Sort.by("gno").descending());

        Page<Guestbook> result = repository.findAll(pageable);

        Function<Guestbook, GuestbookDTO> fn = (entity -> entityToDto(entity));

        return new PageResultDTO<>(result, fn);
    }

    //게시글 조회 메서드드
   @Override
    public GuestbookDTO read(Long gno) {

       Optional<Guestbook> result = repository.findById(gno);

        return result.isPresent() ? entityToDto(result.get()) : null;
        //삼항연사자. GuestbookRepository에서 findById()를 통해 엔티티 객체를 가져온다면
       // 이를 DTO로 바꾸어서 반환한다.
    }

    //게시물 삭제 메서드
    @Override
    public void remove(Long gno){
        repository.deleteById(gno);
    }

    //게시물 수정 메서드
    @Override
    public void modify(GuestbookDTO dto){
        //업데이트 항목은 '제목', '내용'

        //Optional<T> //java.util
        Optional<Guestbook> result = repository.findById(dto.getGno());
        if(result.isPresent()){
            Guestbook entity = result.get();

            entity.changeTitle(dto.getTitle());
            entity.changeContent(dto.getContent());

            repository.save(entity);
        }
    }


    //검색 처리를 위한 BooleanBuilder 생성
    private BooleanBuilder getSearch(PageRequestDTO requestDTO) { //Querydsl 처리

        String type = requestDTO.getType();

        //com.querydsl.core
        BooleanBuilder booleanBuilder = new BooleanBuilder();

        QGuestbook qGuestbook = QGuestbook.guestbook;

        String keyword = requestDTO.getKeyword();

        //com.querydsl.core.types.dsl
        BooleanExpression expression = qGuestbook.gno.gt(0L); //gno > 0 조건에만 생성

        booleanBuilder.and(expression);

        if(type == null || type.trim().length() == 0){ //검색 조건이 없는 경우
            return booleanBuilder;
        }
        

        //검색 조건 작성하기
        BooleanBuilder conditionBuilder = new BooleanBuilder();

        if(type.contains("t")){
            conditionBuilder.or(qGuestbook.title.contains(keyword));
        }

        if (type.contains("c")){
            conditionBuilder.or(qGuestbook.content.contains(keyword));
        }

        if(type.contains("w")){
            conditionBuilder.or(qGuestbook.writer.contains(keyword));
        }

        //모든 조건 통합
        booleanBuilder.and(conditionBuilder);

        return booleanBuilder;
    }
}

 

  • GuestbookServiceImpl에 작성한 getSearch()는 pageRequestDTO를 파라미터로 받는다.
  • 검색 조건(type)이 있는 경우 conditionBuilder 변수를 생성해서 각 검색 조건을 'or'로 연결해서 처리한다.
  • 검색 조건(type)이 없는 경우 'gno > 0'으로만 생성된다.

 

1-3 getList()메서드 수정

 

검색 결과를 목록화 해서 나타내기 위해 getList() 메서드를 수정한다.

 

 

//GuestbookImpl.java

package com.example.guestbook.service;

import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import com.example.guestbook.entity.QGuestbook;
import com.example.guestbook.repository.GuestbookRepository;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.function.Function;

@Service //스프링이 빈으로 처리하도록 @Service 어노테이션 추가
@Log4j2
@RequiredArgsConstructor // <== 의존성 자동 주입!
public class GuestbookServiceImpl implements GuestbookService {

    private final GuestbookRepository repository; //jpa 처리를 위해 repository 주입! 반드시 fianl 사용

    @Override //GuestbookService에서 상속한 메서드 오버라이딩
    public Long register(GuestbookDTO dto) {

        log.info("DTO--------------------------------------");
        log.info(dto);

        Guestbook entity = dtoToEntity(dto); // GuestbookService에서 default를 이용하여 생성한 메서드

        log.info(entity);

        repository.save(entity); // 처리 저장.
//        return null;
        return entity.getGno(); //처리 후에는 엔티티의 gno 리턴
    }

    @Override
    public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestedDTO){
        Pageable pageable = requestedDTO.getPageable(Sort.by("gno").descending());
        //수정
        BooleanBuilder booleanBuilder = getSearch(requestedDTO); //검색조건 처리

        Page<Guestbook> result = repository.findAll(booleanBuilder, pageable); //Querydsl 사용

        Function<Guestbook, GuestbookDTO> fn = (entity -> entityToDto(entity));

        return new PageResultDTO<>(result, fn);
    }

    //게시글 조회 메서드드
   @Override
    public GuestbookDTO read(Long gno) {

       Optional<Guestbook> result = repository.findById(gno);

        return result.isPresent() ? entityToDto(result.get()) : null;
        //삼항연사자. GuestbookRepository에서 findById()를 통해 엔티티 객체를 가져온다면
       // 이를 DTO로 바꾸어서 반환한다.
    }

    //게시물 삭제 메서드
    @Override
    public void remove(Long gno){
        repository.deleteById(gno);
    }

    //게시물 수정 메서드
    @Override
    public void modify(GuestbookDTO dto){
        //업데이트 항목은 '제목', '내용'

        //Optional<T> //java.util
        Optional<Guestbook> result = repository.findById(dto.getGno());
        if(result.isPresent()){
            Guestbook entity = result.get();

            entity.changeTitle(dto.getTitle());
            entity.changeContent(dto.getContent());

            repository.save(entity);
        }
    }


    //검색 처리를 위한 BooleanBuilder 생성
    private BooleanBuilder getSearch(PageRequestDTO requestDTO) { //Querydsl 처리

        String type = requestDTO.getType();

        //com.querydsl.core
        BooleanBuilder booleanBuilder = new BooleanBuilder();

        QGuestbook qGuestbook = QGuestbook.guestbook;

        String keyword = requestDTO.getKeyword();

        //com.querydsl.core.types.dsl
        BooleanExpression expression = qGuestbook.gno.gt(0L); //gno > 0 조건에만 생성

        booleanBuilder.and(expression);

        if(type == null || type.trim().length() == 0){ //검색 조건이 없는 경우
            return booleanBuilder;
        }


        //검색 조건 작성하기
        BooleanBuilder conditionBuilder = new BooleanBuilder();

        if(type.contains("t")){
            conditionBuilder.or(qGuestbook.title.contains(keyword));
        }

        if (type.contains("c")){
            conditionBuilder.or(qGuestbook.content.contains(keyword));
        }

        if(type.contains("w")){
            conditionBuilder.or(qGuestbook.writer.contains(keyword));
        }

        //모든 조건 통합
        booleanBuilder.and(conditionBuilder);

        return booleanBuilder;
    }
}

 

1-4. 테스트 코드로 확인(getSearch의 정상작동 여부)

 

//GuestbookServiceTests.java

package com.example.guestbook.service;

import com.example.guestbook.dto.GuestbookDTO;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.dto.PageResultDTO;
import com.example.guestbook.entity.Guestbook;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.sql.SQLOutput;

@SpringBootTest
public class GuestbookServiceTests {
    @Autowired
    private GuestbookService service;

    @Test
    public void testRegister(){
        //테스트 객체 생성
        GuestbookDTO guestbookDTO = GuestbookDTO.builder()
                .title("Sample title...")
                .content("Sample Content...")
                .writer("user0")
                .build();

        //service.register() 테스트: 테스트 객체 사용
        System.out.println(service.register(guestbookDTO));
    }

    @Test
    public void testList(){ //목록처리 테스트
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
                .page(1)
                .size(10)
                .build();

        PageResultDTO<GuestbookDTO, Guestbook> resultDTO = service.getList(pageRequestDTO);

        //테스트에 값 확인 추가
        System.out.println("PREV " + resultDTO.isPrev());
        System.out.println("NEXT " + resultDTO.isNext());
        System.out.println("TOTAL: " + resultDTO.getTotalPage());




        for(GuestbookDTO guestbookDTO : resultDTO.getDtoList()) {
            System.out.println(guestbookDTO);
        }

        System.out.println("====================================");
        resultDTO.getPageList().forEach(i -> System.out.println(i));
    }
	
    @Test	//검색 테스트 (getList -> getSearch 테스트)
    public void testSearch(){
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
                .page(1)
                .size(10)
                .type("tc") //검색조건 t, c, w, tc, tcw..
                .keyword("한글") //검색 키워드드
               .build();

        PageResultDTO<GuestbookDTO, Guestbook> resultDTO = service.getList(pageRequestDTO);

        System.out.println("PREV" + resultDTO.isPrev());
        System.out.println("NEXT" + resultDTO.isNext());
        System.out.println("TOTAL" + resultDTO.getTotalPage());

        System.out.println("----------------------------------------------");

        for (GuestbookDTO guestbookDTO : resultDTO.getDtoList()){
            System.out.println("guestbookDTO");
        }
    }
}

위의 테스트 코드는 제목(t)이나 내용(c)에 '한글'이라는 키워드가 있는 글을 검색한다.

테스트를 진행하면 아래와 같은 쿼리가 실행된다.

 

 

쿼리의 where절 안쪽에서는 검색조건이 처리된다.

Hibernate: 
    select
        guestbook0_.gno as gno1_0_,
        guestbook0_.moddate as moddate2_0_,
        guestbook0_.reg_date as reg_date3_0_,
        guestbook0_.content as content4_0_,
        guestbook0_.title as title5_0_,
        guestbook0_.writer as writer6_0_ 
    from
        guestbook guestbook0_ 
    where
        guestbook0_.gno>? 
        and (
            guestbook0_.title like ? escape '!' 
            or guestbook0_.content like ? escape '!'
        ) 
    order by
        guestbook0_.gno desc limit ?
PREVfalse
NEXTfalse
TOTAL0
----------------------------------------------

gno > 0 조건은 바깥쪽으로 처리되었다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments