일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 네트워크 설정
- 자료구조와함께배우는알고리즘입문
- ㅒ
- 선형대수
- network configuration
- 코드로배우는스프링부트웹프로젝트
- resttemplate
- GIT
- iterator
- 자료구조와 함께 배우는 알고리즘 입문
- 스프링부트핵심가이드
- d
- 코드로배우는스프링웹프로젝트
- 자바편
- 페이징
- 목록처리
- 처음 만나는 AI 수학 with Python
- 처음 만나는 AI수학 with Python
- 구멍가게코딩단
- 알파회계
- 친절한SQL튜닝
- Kernighan의 C언어 프로그래밍
- /etc/network/interfaces
- 티스토리 쿠키 삭제
- baeldung
- 스프링 시큐리티
- 데비안
- 이터레이터
- 서버설정
- 리눅스
- Today
- Total
bright jazz music
guestbook : 06. 목록처리(3) 컨트롤러와 화면에서의 목록처리 본문
● 이전 포스팅에서 서비스 계층까지의 등록작업과 목록 처리가 완료되었다.
이 포스팅에서는 컨트롤러와 화면구성과 관련된 내용을 다룬다.
1. Controller 클래스 수정
GuestbookController 클래스 수정
//GuestbookController.java
package com.example.guestbook.controller;
import com.example.guestbook.dto.PageRequestDTO;
import com.example.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/guestbook")
@Log4j2
@RequiredArgsConstructor //자동 주입을 위한 어노테이션
public class GuestbookController {
private final GuestbookService service; // final로 선언
@GetMapping("/")
public String index(){
return "redirect:/guestbook/list";
}
@GetMapping("/list")
public String list(PageRequestDTO pageRequestDTO, Model model){
log.info("list...................." + pageRequestDTO);
// model에 result를 key로 담아서 list 페이지에 뿌려준다.
model.addAttribute("result", service.getList(pageRequestDTO));
return "/guestbook/list";
}
/*
SpringDAta JPA를 이용하는 경우 @Pageable 어노테이션으로 Pageable 타입을 이용할 수도 있고,
application.properties에 0이 아닌 1부터 페이지 번호를 시작하도록 받을 수 있도록 처리할 수도 있다.
예제에서는 그냥 0부터 받는 방식을 사용하였다. 추후 검색조건 등과 같이 추가로 전달되어야 하는 데이터가
많을 경우 더욱 복잡해질 수 있기 때문이다.
*/
}
SpringDAta JPA를 이용하는 경우 @Pageable 어노테이션으로 Pageable 타입을 이용할 수도 있고, application.properties에 0이 아닌 1부터 페이지 번호를 시작하도록 받을 수 있도록 처리할 수도 있다.
예제에서는 그냥 0부터 받는 방식을 사용하였다. 추후 검색조건 등과 같이 추가로 전달되어야 하는 데이터가 많을 경우 더욱 복잡해질 수 있기 때문이다.
------------- 오류 발생 -------------
문제발생: 테스트용 페이지의 RegDate 값이 가져와지지 않음.
원인 1 :
DTO의 속성의 형식 값을 잘못 입력함. LocalDateTime으로 입력했어야 하는데 LocalTime으로 입력. LocalDateTime으로 수정함. 자동완성 기능을 믿고 제대로 확인하지 않아 발생한 실수.
==> LocalTime에서 LocalDateTime으로 수정함.
package com.example.guestbook.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GuestbookDTO {
private Long gno;
private String title;
private String content;
private String writer;
private LocalDateTime regDate; //LocalTime이 아니라 LocalDateTime
private LocalDateTime modDate; //LocalTime이 아니라 LocalDateTime
/*
* - GuestbookDTO는 GuestbookEntity와 거의 동일한 필드를 가지고 있다.
* - @Data 어노테이션을 사용해 getter/setter로 값 변경이 가능하도록 구성
* - 서비스 계층에서 이 DTO를 이용해 필요한 내용을 전달받고 반환한다.
*
* 이러한 처리를 가능하도록 하기 위해 service 패키지를 생성하고
* GuestbookService와 GuestbookServiceImple 클래스를 작성한다.
*
*
* */
}
원인 2 :
JpaRepository를 상속하는 Repository클래스의 메소드에서 해당 속성값을 가져오는 코드를 써주지 않았음. builder() 메서드 사용 시에 해당 속성값을 넣어주지 않았다는 뜻임. 따라서 객체가 생성될 때 값이 지정되지 않았음.
==> builder()메서드에 코드를 넣어줌.
//GuestbookService.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.springframework.stereotype.Service;
//@Service 어노테이션은 GuestbookImple에 적어준다.
public interface GuestbookService { //GuestbookImple 클래스에서 이 인터페이스를 상속한다.
Long register(GuestbookDTO dto); // GuestbookImpl 클래스에서 오버라이딩
//getList()메서드
PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestDTO);
//dto에서 Entity로의 변환작업
default Guestbook dtoToEntity(GuestbookDTO dto) {
Guestbook entity = Guestbook.builder()
.gno(dto.getGno())
.title(dto.getTitle())
.content(dto.getContent())
.writer(dto.getWriter())
.build();
return entity;
}
//Entity에서 DTO로의 변환작업
default GuestbookDTO entityToDto(Guestbook entity) {
GuestbookDTO dto = GuestbookDTO.builder()
.gno(entity.getGno())
.title(entity.getTitle())
.content(entity.getContent())
.writer(entity.getWriter())
.regDate(entity.getRegDate()) //빼먹어서 추가함
.modDate(entity.getModDate()) //빼먹어서 추가함
.build();
return dto;
}
}
/*
* default 메서드:
* 인터페이스의 실제 내용을 가지는 코드를 default라는 키워드로 생성 가능.
* default 메서드를 이용하면 기존에 추상 클래스를 통해 전달해야 하는 실제 코드를
* 인터페이스에 선언할 수 있음.
*
* '인터페이스 -> 추상클래스 -> 구현 클래스'의 형태에서 추상클래스를 생략하는 것이 가능해짐.
* */
------------- 오류 수정 완료 -------------
2. 화면에서 컨트롤러에서 보낸 데이터 받도록 구성(list.html 수정)
<!--list.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1>GuestBook List Page!!!!</h1>
<table class="table table-striped"> <!--테이블이 줄무늬로 나옴. table-hover 하면 커서가 올라가면 색 바뀜 -->
<thead>
<tr>
<th scope="col">#</th>
<!-- <th scope="col">Gno</th>-->
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}"> <!--PageResultDTO안에 들어있는 dtoList 반복처리-->
<th scope="row">[[${dto.gno}]]</th>
<td>[[${dto.title}]]</td>
<td>[[${dto.writer}]]</td>
<!-- <td>[[${dto.regDate}]]</td>-->
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td><!--등록일자를 포맷으로 출력-->
</tr>
</tbody>
</table>
</th:block>
</th:block>
실행화면
3. 목록페이지 처리
정상적으로 페이지 이동이 가능하도록 처리
- 화면 아래 쪽에 구성
- 클릭 시 페이지 이동 처리
http://localhost:8080/guestbook/list?page=2
//이렇게 쿼리스트링을 넣는 경우 2페이지가 출력됨
페이지 목록 추가. </table> 태그 아래에 태그 추가
<!--list.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1>GuestBook List Page!!!!</h1>
<table class="table table-striped"> <!--테이블이 줄무늬로 나옴. table-hover 하면 커서가 올라가면 색 바뀜 -->
<thead>
<tr>
<th scope="col">#</th>
<!-- <th scope="col">Gno</th>-->
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}"> <!--PageResultDTO안에 들어있는 dtoList 반복처리-->
<th scope="row">[[${dto.gno}]]</th>
<td>[[${dto.title}]]</td>
<td>[[${dto.writer}]]</td>
<!-- <td>[[${dto.regDate}]]</td>-->
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td><!--등록일자를 포맷으로 출력-->
</tr>
</tbody>
</table>
<!-- 페이지 이동을 위한 태그-->
<ul class="pagination h-100 justify-content-center align-items-center"> <!-- ul 태그 안에는 전부 적용 -->
<!--h-100: 가로로 표시, justify-content-center: 페이지의 중앙에 표시 왼쪽은 start 오른쪽은 end-->
<li class="page-item" th:if="${result.prev}">
<a class="page-link" href="#" tabindex="-1">Previous!</a>
<!-- pageSize가 10이기 떄문에 11페이지 이상부터 Previous! 버튼이 나타난다.-->
</li>
<li th:class=" 'page-item ' + ${result.page == page?'active':''} "th:each="page: ${result.pageList}">
<a class="page-link" href="#">
[[${page}]]
</a>
</li>
<li class="page-item" th:if="${result.next}">
<a class="page-link" href="#">Next!</a>
</li>
</ul>
<!-- 추가 태그 끝
'이전(previous)'과 '다음(next)' 부분은 Thymeleaf의 if를 이용해서 처리.
페이지 중간에 현재 페이지 여부를 체크해서 'active'라는 이름의 클래스가 출력되도록 작성
-->
</th:block>
</th:block>
적용 후 재 구동
아직 링크를 연결하지 않았기 때문에 페이지 번호 버튼을 눌러도 페이지가 이동하지 않는다. url에 쿼리스트링을 추가하여 페이지를 이동할 수 있다.
4. 페이지 번호 링크 처리
list.html 파일의 내용을 수정한다. 링크를 연결해야 하기 때문에 href 태그를 수정해야 한다.
href="#"를 아래와 같이 수정한다. 클릭하면 쿼리스트링 형식으로 전달되도록 만드는 것이다.
previous!의 경우
th:href="@{/guestbook/list(page = ${result.start -1 })}"
현재페이지의 경우
th:href="@{/guestbook/list(page = ${page})}"
Next!의 경우
th:href="@{/guestbook/list(page = ${result.end +1 })}"
아래는 수정 반영 완료한 내용이다.
<!--list.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1>GuestBook List Page!!!!</h1>
<table class="table table-striped"> <!--테이블이 줄무늬로 나옴. table-hover 하면 커서가 올라가면 색 바뀜 -->
<thead>
<tr>
<th scope="col">#</th>
<!-- <th scope="col">Gno</th>-->
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">Regdate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}"> <!--PageResultDTO안에 들어있는 dtoList 반복처리-->
<th scope="row">[[${dto.gno}]]</th>
<td>[[${dto.title}]]</td>
<td>[[${dto.writer}]]</td>
<!-- <td>[[${dto.regDate}]]</td>-->
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td><!--등록일자를 포맷으로 출력-->
</tr>
</tbody>
</table>
<!-- 페이지 이동을 위한 태그-->
<ul class="pagination h-100 justify-content-center align-items-center"> <!-- ul 태그 안에는 전부 적용 -->
<!--h-100: 가로로 표시, justify-content-center: 페이지의 중앙에 표시 왼쪽은 start 오른쪽은 end-->
<li class="page-item" th:if="${result.prev}">
<a class="page-link" th:href="@{/guestbook/list(page= ${result.start -1 })}"
tabindex="-1">Previous!</a>
<!-- pageSize가 10이기 떄문에 11페이지 이상부터 Previous! 버튼이 나타난다.-->
</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})}">
[[${page}]]
</a>
</li>
<li class="page-item" th:if="${result.next}">
<a class="page-link" th:href="@{/guestbook/list(page= ${result.end + 1})}">Next!</a>
</li>
</ul>
<!-- 추가 태그 끝
'이전(previous)'과 '다음(next)' 부분은 Thymeleaf의 if를 이용해서 처리.
페이지 중간에 현재 페이지 여부를 체크해서 'active'라는 이름의 클래스가 출력되도록 작성
-->
</th:block>
</th:block>
실행화면
- Previous! : 누르면 이전 10페이지 목록 앞으로 이동한다. p26에서 Previous! 버튼을 누르면 p20으로 이동한다. 첫 목록 페이지로 이동하면 Previous! 버튼이 사라진다
- Next! : 누르면 다음 10페이지 목록 다음으로 이동한다. p16에서 Next! 버튼을 누르면 p21로 이동한다. 마지막 목록 페이지로 이동하면 Next! 버튼이 사라진다.
페이지를 요청하는 절차
- 컨트롤러(/guestbook) -> (/) ->(/list)로 리다이렉트
- PageRequestDTO를 서비스 계층으로 전달. (service.getList(pageRequestDTO))
- 서비스구현 계층에서 getList(pageRequestDTO)를 @Override:
- DB에서 데이터를 가져와서 entity로 저장.
- 이를 다시 PageRequest
-
- 화면에서 페이지 번호, Previous!, Next! 버튼 클릭
- url로 쿼리스트링이 GuestbookController로 전달됨. (/guestbook) -> (/) ->(/list)로 리다이렉트
- PageRequestDTO를 서비스 계층으로 전달(service.getList(pageRequestDTO))
- 서비스구현 계층에서 getList(pageRequestDTO)를 @Override :
@Override
public PageResultDTO<GuestbookDTO, Guestbook> getList(PageRequestDTO requestedDTO){
Pageable pageable = requestedDTO.getPageable(Sort.by("gno").descending());
//PageRequest를 반환. 그걸 pageable로 객체로 변환
Page<Guestbook> result = repository.findAll(pageable);
//쿼리메서드의 객체로 Pageable 객체를 전달.
// 엔티티를 반환
Function<Guestbook, GuestbookDTO> fn = (entity -> entityToDto(entity));
//엔티티를 entityToDto 메서드에 전달.
return new PageResultDTO<>(result, fn);
}
s
//PageRequestDTO.java
public Pageable getPageable(Sort sort) {
return PageRequest.of(page - 1, size, sort );
//PageRequest.of는 파라미터를 반영한 PageRequest를 반환한다.
//이를 Pageable 객체로 반환한다.
//이는 PageRequest가 AbstractPageRequest를 상속하기 때문이다.
//AbstractPageRequest는 Pageable 터페이스, Serializable 인터페이스를 구현한다.
}
//public class PageRequest extends AbstractPageRequest
// AbstractPageRequest는 Pageable 인터페이스, Serializable 인터페이스를 상속하는 추상 클래스이다.
// PageRequest는 이 AbstractPageRequest 추상클래스를 상속한다.
public static PageRequest of(int page, int size, Sort sort) {
return new PageRequest(page, size, sort); // PageRequest 반환!!!!!!!!!!!!!
}
/**
* Creates a new {@link PageRequest} with sort direction and properties applied.
*
* @param page zero-based page index, must not be negative.
* @param size the size of the page to be returned, must be greater than 0.
* @param direction must not be {@literal null}.
* @param properties must not be {@literal null}.
* @since 2.0
*/
//Entity에서 DTO로의 변환작업
default GuestbookDTO entityToDto(Guestbook entity) {
GuestbookDTO dto = GuestbookDTO.builder()
.gno(entity.getGno())
.title(entity.getTitle())
.content(entity.getContent())
.writer(entity.getWriter())
.regDate(entity.getRegDate())
.modDate(entity.getModDate())
.build();
return dto;
'Framework > Spring' 카테고리의 다른 글
guestbook : 07. 등록페이지와 등록처리 (2) (0) | 2022.07.17 |
---|---|
guestbook : 07. 등록페이지와 등록처리 (1) (0) | 2022.07.11 |
guestbook : 06. 목록처리(2) 데이터 페이지 처리 (0) | 2022.07.06 |
guestbook : 06. 목록처리(1) 목록 가져오기 (0) | 2022.07.05 |
guestbook : 05. DTO를 사용한 Querydsl 테스트 (0) | 2022.07.01 |