Spring/코드로 배우는 스프링부트 웹 프로젝트

코드로 배우는 스프링부트 웹 프로젝트 Day 11

한 면만 쓴 종이 2022. 8. 26. 17:57

/* Annotation들은 스프링부트 프로젝트의 Annotation 정리 페이지에 따로 정리해두었습니다. */

 

GuestbookServiceImpl에서 목록을 조회할 때 사용하는 getList()는 기존의 코드를 조금 수정해서 작성

 

GuestbookServiceImpl 클래스 수정

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

    BooleanBuilder booleanBuilder = getSearch(requestDTO); // 검색 조건 처리

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

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

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

 

테스트 코드로 검색이 잘 이루어지는지 확인

 

GuestbookServiceTests 수정

@Test
public void testSearch() {
    
    PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
            .page(1)
            .size(10)
            .type("tc") // 검색 조건 t, c, w, tc, tw, ...
            .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);
    }

    System.out.println("=================================================");
    resultDTO.getPageList().forEach(System.out::println);
}

제목이나 내용에 "한글" 키워드가 있는지 검색하는 테스트

 

테스트 과정에서 실행된 쿼리 + 결과

gno > 0 은 쿼리의 where절에서 처리되지 않는 것을 확인할 수 있음

 

 

[목록 페이지 검색 처리]

검색 처리를 하기 위해서는 화면에 검색 타입과 키워드를 입력할 수 있는 UI가 필요함

 

list.html에 검색 타입과 키워드를 입력할 공간과, 검색 버튼을 추가

 

list.html

<form action="/guestbook/list" method="get" id="searchForm">
    <div class="input-group">
        <input type="hidden" name="page" value="1">
        <div class="input-group-prepend">
            <select class="custom-select" name="type">
                <option th:selected="${pageRequestDTO.type == null}">------</option>
                <option value="t" th:selected="${pageRequestDTO.type == 't'}">제목</option>
                <option value="c" th:selected="${pageRequestDTO.type == 't'}">내용</option>
                <option value="w" th:selected="${pageRequestDTO.type == 't'}">작성자</option>
                <option value="tc" th:selected="${pageRequestDTO.type == 't'}">제목 + 내용</option>
                <option value="tcw" th:selected="${pageRequestDTO.type == 't'}">제목 + 내용 + 작성자</option>
            </select>
        </div>
        <input class="form-control" name="keyword" th:value="$pageRequestDTO.keyword}">
        <div class="input-group-append" id="button-addon4">
            <button class="btn btn-outline-secondary btn-search" type="button">Search</button>
            <button class="btn btn-outline-secondary btn-clear" type="button">Clear</button>
        </div>
    </div>
</form>

<input type="hidden"> 부분에서 page의 값을 1로 지정한 것은 검색을 진행했을 때 무조건 1페이지를 보여주도록 하기 위함

 

그런데 실행했을 때 type 설정 부분이 이상했다..

/guestbook/list

<select class="custom-select" name="type" style="height:38px">

일단 style로 height를 지정해주었다..

 

잘 맞춰지긴 했다.

 

그런데 지금 발견한 더 이상한 점이 있다.

Gno가 아니라 Title, Title이 아니라 Writer이 항목 이름이어야 하는데..

 

바꿨다.

 

그리고 버튼에 따른 작업 처리도 해주었다.

<script th:inline="javascript">

    var msg = [[${msg}]];

    console.log(msg);

    if(msg){
        $(".modal").modal();
    }

    var searchForm = $("#searchForm");

    $('.btn-search').click(function(e) {
        searchForm.submit();
    });

    $('.btn-clear').click(function(e) {
        searchForm.empty().submit();
    });

</script>

clear을 누르면, 모든 검색 관련 내용이 사라지고 목록 1페이지로 이동함

 

search를 누르면 검색한 내용의 목록 중 1페이지로 이동함

Search

 

[페이지 번호와 검색 조건 추가]

 

목록 페이지의 하단의 검색은 현재 page값만 처리하고 있기 때문에 type과 keyword를 추가해준다.

 

list.html

<ul class="pagination h-100 justify-content-center align-items-center">

    <li class="page-item" th:if="${result.prev}">
        <a class="page-link" th:href="@{/guestbook/list(page= ${result.start -1}, type=${pageRequestDTO.type}, keyword = ${pageRequestDTO.keyword})}" tabindex="-1">Previous</a>
    </li>

    <li th:class=" 'page-item ' + ${result.page == page?'active':''} " th:each="page: ${result.pageList}">
        <a class="page-link" th:href="@{/guestbook/list(page = ${page}, type=${pageRequestDTO.type}, keyword = ${pageRequestDTO.keyword})}">
            [[${page}]]
        </a>
    </li>

    <li class="page-item" th:if="${result.next}">
        <a class="page-link" th:href="@{/guestbook/list(page = ${result.end + 1}, type=${pageRequestDTO.type}, keyword = ${pageRequestDTO.keyword})}">Next</a>
    </li>

</ul>

 

페이지 번호들

 

[조회 페이지로 이동하는 검색 처리]

 

특정 글의 번호를 클릭해서 이동하는 검색

    => type과 keyword 항목을 추가해서 처리 가능

 

list.html

<tr th:each="dto : ${result.dtoList}">
    <th scope="row">
        <a th:href="@{/guestbook/read(gno = ${dto.gno}, page= ${result.page}, type=${pageRequestDTO.type}, keyword = ${pageRequestDTO.keyword})}">
            [[${dto.gno}]]
        </a>

위처럼 하면 type과 keyword도 파라미터로 추가 전송됨

 

[조회 페이지 검색 처리]

 

기존 조회 페이지는 page값만 처리해서 다시 목록으로 돌아가는 링크를 앞서 처리한 것과 동일하게 type과 keyword를 추가해야 함

 

read.html

<a th:href="@{/guestbook/modify(gno = ${dto.gno}, page=${requestDTO.page}, type=${requestDTO.type}, keyword=${requestDTO.keyword})}">
    <button type="button" class="btn btn-primary">Modify</button>
</a>

<a th:href="@{/guestbook/list(page=${requestDTO.page}, type=${requestDTO.type}, keyword=${requestDTO.keyword})}">
    <button type="button" class="btn btn-info">List</button>
</a>

이렇게 하면 목록 페이지에서 특정 검색 조건으로 검색한 후 특정 글로 이동 후, 목록 버튼을 누르면 이전에 검색 조건으로 검색한 페이지로 돌아갈 수 있음

 

[수정 작업 후 이동 처리]

 

GuestbookController는 작업이 끝난 후에 redirect로 이동하는 경우가 있음

  • 등록 처리: 1페이지로 이동
  • 삭제 처리: 1페이지로 이동
  • 수정 처리: 조회 페이지로 이동

수정은 조회 페이지로 이동하기 때문에 검색 조건을 유지해야 함

    => page, type, keyword 처리해야 함

 

modify.html

<form action="/guestbook/modify" method="post">

    <!-- 페이지 번호 -->
    <input type="hidden" name="page" th:value="${requestDTO.page}">
    <input type="hidden" name="type" th:value="${requestDTO.type}">
    <input type="hidden" name="keyword" th:value="${requestDTO.keyword}">

type, keyword 추가함

$(".listBtn").click(function () {

    var page = $("input[name='page']");
    var type = $("input[name='type']");
    var keyword = $("input[name='keyword']");

    actionForm.empty(); //form 태그의 모든 내용을 지움
    
    actionForm.append(page); //목록 페이지 이동에 필요한 내용을 다시 추가
    actionForm.append(type);
    actionForm.append(keyword);

    actionForm
        .attr("action", "/guestbook/list")
        .attr("method", "get");

    actionForm.submit();
});

type과 keyword도 <form> 태그와 같이 전송하도록 수정함

 

GuestbookController 클래스

@PostMapping("/modify")
public String modify(GuestbookDTO dto, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, RedirectAttributes redirectAttributes) {

    log.info("post modify..................................................");
    log.info("dto: " + dto);

    service.modify(dto);

    redirectAttributes.addAttribute("page", requestDTO.getPage());
    redirectAttributes.addAttribute("type", requestDTO.getType());
    redirectAttributes.addAttribute("keyword", requestDTO.getKeyword());
    redirectAttributes.addAttribute("gno" ,dto.getGno());

    return "redirect:/guestbook/read";
}

redirect 처리 될 때 검색 조건을 유지하도록 type과 keyword를 추가함