1. 페이징 처리를 위한 필드들 정의 (Pagination.class)
2. 총 컨텐츠 개수, 현재 페이지, 현재 페이지 블록(범위) 를 받아서 처리
3. 화면에 보여주기(View)
1. 프로젝트 구조
2. Pagination, Common 코드
@Data
public class Pagination extends Common{
private int listSize = 10; // 초기값으로 목록개수를 10으로 셋팅
private int rangeSize = 10; // 초기값으로 페이지범위를 10으로 셋팅
private int page; // 현재 페이지
private int range; // 현재 페이지 범위 (1~10 = 1)
private int listCnt; // 총 게시물 개수
private int pageCnt; // 총 페이지 개수
private int startPage; // 각 페이지 범위 중 시작 번호
private int startList; // mysql용 게시판 시작 번호 (0, 10, 20..)
private int endPage; // 각 페이지 범위 중 마지막 번호
private boolean prev; // 이전 페이지 여부
private boolean next; // 다음 페이지 여부
public void pageInfo(int page, int range, int listCnt) {
this.page = page; // 현재 페이지
this.listCnt = listCnt; // 게시물 개수 총합
// 페이지 범위
this.range = range;
// 전체 페이지수
this.pageCnt = (int) Math.ceil((double) listCnt / listSize);
// 시작 페이지
this.startPage = (range - 1) * rangeSize + 1 ;
// 끝 페이지
this.endPage = range * rangeSize;
// 게시판 시작번호
this.startList = (page - 1) * listSize;
// 이전 버튼 상태
this.prev = range == 1 ? false : true;
// 다음 버튼 상태
this.next = endPage > pageCnt ? false : true;
if(this.endPage > this.pageCnt) {
this.endPage = this.pageCnt;
this.next = false;
}
}
}
- Pagination : 페이지 처리에 필요한 필드 Common을 상속받고 활용할 외부 정보를 저장
package com.min.board.paging;
import lombok.Data;
@Data
public class Common {
private String searchText;
private String writer;
private String type;
}
- Common : 메인 게시판, 내 글 관리, 휴지통 등 컨텐츠를 보여줄 때 아래 페이지 처리를 해야하는데 이 때 사용
- searchText : 검색 기능이 필요한 페이지의 경우 사용
- writer : 내 글 관리, 휴지통 등에서 사용자 정보를 저장
- type : 메인 게시판, 내 글 관리, 휴지통 등 DB에서 가져오는 값이 다르므로 Mapper에서 <if> 문으로 동적 쿼리 진행
3. Mapper 코드
@Mapper
public interface BoardMapper {
// 게시글 개수 반환 (메인 게시글, 글 관리, 휴지통)
int selectBoardTotalCount(Pagination pagination);
// 게시글 리스트 (메인 게시글, 글 관리, 휴지통)
List<Board> selectBoardList(Pagination pagination);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.min.board.repository.BoardMapper">
<sql id="boardColumns">
id
,title
,content
,writer_id
,writer
,delete_yn
,create_date
,image
</sql>
<!--(페이징) 게시글 모두 조회-->
<select id="selectBoardList" resultType="Board">
SELECT
<include refid="boardColumns"/>
FROM
tb_board
WHERE
<if test="'list'.equals(type)">
<include refid="CommonMapper.search"/>
</if>
<if test="'myPost'.equals(type)">
<include refid="CommonMapper.myPost"></include>
</if>
<if test="'trash'.equals(type)">
<include refid="CommonMapper.trash"></include>
</if>
ORDER BY
id DESC,
create_date
<include refid="CommonMapper.paging"/>
</select>
<!--(페이징을 위한 카운트) 게시글 개수 카운트-->
<select id="selectBoardTotalCount" resultType="int">
SELECT
COUNT(*)
FROM
tb_board
WHERE
<if test="'list'.equals(type)">
<include refid="CommonMapper.search"/>
</if>
<if test="'myPost'.equals(type)">
<include refid="CommonMapper.myPost"></include>
</if>
<if test="'trash'.equals(type)">
<include refid="CommonMapper.trash"></include>
</if>
</select>
</mapper>
<BoardMapper.xml>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="CommonMapper">
<sql id="paging">
LIMIT
#{startList}, #{listSize}
</sql>
<sql id="search">
delete_yn = 'N'
<if test="searchText != null and searchText !=''">
AND
(
title LIKE CONCAT('%', #{searchText}, '%')
OR content LIKE CONCAT('%', #{searchText}, '%')
)
</if>
</sql>
<sql id="myPost">
delete_yn = 'N'
<if test="writer != null and writer !=''">
AND writer = #{writer}
</if>
</sql>
<sql id="trash">
delete_yn = 'Y'
<if test="writer != null and writer !=''">
AND writer = #{writer}
</if>
</sql>
</mapper>
<CommonMapper.xml>
- 코드관리를 위해 CommonMapper로 동적 쿼리 진행할 코드를 분리
4. Service 코드
// 전체 게시글 개수 리턴
public int getBoardListCnt(Pagination pagination) {
int boardTotalCount = 0;
try {
boardTotalCount = boardRepository.selectBoardTotalCount(pagination);
} catch (Exception e) {
System.out.println("boardRepository.getBoardListCnt() .. error : " + e.getMessage());
} finally {
return boardTotalCount;
}
}
// 전체 게시글 리스트로 리턴
public List<Board> getBoardList(Pagination pagination) {
List<Board> boards = Collections.emptyList();
try {
boards = boardRepository.selectBoardList(pagination);
} catch (Exception e) {
System.out.println("boardRepository.getMyBoardList() .. error : " + e.getMessage());
} finally {
return boards;
}
}
5. Controller 코드
// 게시판 리스트 (게시글 페이징 및 검색 리스트)
@GetMapping("/list")
public String list(Model model,
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "1") int range,
String searchText) {
int listCount = 0;
Pagination pagination = new Pagination();
pagination.setSearchText(searchText);
pagination.setType("list");
listCount = boardService.getBoardListCnt(pagination);
pagination.pageInfo(page, range, listCount);
List<Board> boards = boardService.getBoardList(pagination);
model.addAttribute("pagination", pagination);
model.addAttribute("boardList", boards);
return "board/list";
}
- 메인 게시판에 페이징 처리를 위해 page, range, searchText를 받아온다.
- pagination에 검색어, type(게시판 = list, 내가쓴글 = myPost, 휴지통 = trash 등을 선언해 type에 맞는 동적 쿼리문 실행 유도)을 저장
- listCount에 pagination 객체를 이용해서 총 게시글 개수를 저장
- 최종적으로 Pagination 객체에 page, range, listCount를 초기화
- List 형태의 boards 를 생성해서 현재 페이지에 맞는 컨텐츠를 받아옴.
// 글 관리에서 삭제
@PostMapping("/myPost/delete")
public String boardDelete(@RequestParam(required = false) List<String> boardIdList) {
if(boardIdList == null) return "redirect:/board/myPost";
if(boardIdList.size() > 0) {
for(int i = 0; i < boardIdList.size(); i ++) {
boardService.temporaryDelete(Long.parseLong(boardIdList.get(i)));
}
}
return "redirect:/board/myPost";
}
6. View
<p> 총 건수 : <span th:text="${pagination.listCnt}"></span></p>
<form class="row g-3 justify-content-end" method="GET" th:action="@{/board/list}" name="searchForm">
<div class="col-auto">
<input type="hidden" name="page" th:value="${param.page}">
<input type="hidden" name="range" th:value="${param.range}">
<label for="searchText" class="visually-hidden"></label>
<input type="text" class="form-control" id="searchText" name="searchText" th:value=${param.searchText}>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-outline-secondary mb-3">Search</button>
</div>
</form>
.
.
.
<tbody>
<tr th:each="board : ${boardList}">
<td class="mt-5 text-center" scope="row" th:text="${board.id}">1</td>
<td><a th:text="${board.title}" th:href="@{/board/post(id=${board.id})}" style="text-decoration:none; color:black;">제목</a></td>
<td class="text-center" th:text="${#dates.format(board.createDate, 'yyyy/MM/dd HH:mm')}">작성일</td>
<td class="text-center" th:text="${board.writer}">작성자</td>
</tr>
</tbody>
</table>
<!-- Page -->
<nav th:replace="fragments/pagingCommon :: pagination(${pagination}, 'list')"></nav>
<list.html>
- pagination.listCnt : 보여줄 게시글의 총 개수
- input - hidden : name으로 컨트롤러로 page, range를 전송 (GET 방식이므로 url에 표시)
- value=${param.range}" : url에 표시되는 range=3 값을 가져와 해당 input태그 값으로 셋팅
- input - searchText : 컨트롤러로 searchText를 전송해서 결과값을 얻도록 유도 (th:value를 이용해서 검색후에도 해당 칸에 검색어가 그대로 남아있음)
- <tbody> : 테이블에 게시판 글을 보여주기위한 컨텐츠
- th:each="board : ${boardList}"> : 컨트롤러에서 받아온 List형태인 boardList를 변수명 board로 정의해서 리스트 값을 꺼내서 사용
- ${dates.format(board.createDate, 'yyyy/MM/dd HH:mm')} : 날짜 형식을 2021/12/28 19:48 로 변경
[Spring Boot] Timestamp로 회원가입 시간 저장 (MySQL)
1. MySQL 시간을 담을 컬럼은 DATETIME으로 설정 2. DTO에 Timestamp 타입으로 선언 private Timestamp createDate; 3. 생성 시점에 시간 셋팅해주기 member.setCreateDate(Timestamp.valueOf(LocalDateTime.now()..
black-mint.tistory.com
- th:replace="fragments/pagingCommon :: pagination(${pagination}, 'list')" : 프래그먼트를 이용해 분리된 페이징 화면을 불러와 사용
[Spring Boot] Thymeleaf 정리
스프링부트에서 타임리프로 프로젝트를 진행 중 2번 이상 다시 찾아본 개념에 대해 정리해놓으려 합니다. 기본 타임리프 문서 : https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#standard-htmlx..
black-mint.tistory.com
<!-- contents -->
<form class="row g-3 justify-content-end" method="post" th:action="@{/board/myPost/delete}">
<table class="table caption-top table-bordered table-hover">
<caption>List</caption>
<thead>
<tr>
<th class="text-center" width="50" scope="col"></th>
<th class="text-center" width="50" scope="col">No</th>
<th class="text-center" width="950" scope="col">제목</th>
<th class="text-center" width="200" scope="col">작성일</th>
<th class="text-center" width="180" scope="col">작성자</th>
</tr>
</thead>
<tbody>
<tr th:each="board : ${boardList}">
<td class="mt-5 text-center" scope="row">
<div class="checkbox">
<input type="checkbox" name="boardIdList" th:value="${board.id}">
</div>
</td>
<td class="mt-5 text-center" scope="row" th:text="${board.id}">1</td>
<td><a th:text="${board.title}" th:href="@{/board/post(id=${board.id})}"
style="text-decoration:none; color:black;">제목</a></td>
<td class="text-center" th:text="${#dates.format(board.createDate, 'yyyy/MM/dd HH:mm')}">작성일
</td>
<td class="text-center" th:text="${board.writer}">작성자</td>
</tr>
</tbody>
</table>
<div class="nav justify-content-end">
<button type="submit" class="btn btn-danger">Delete</button>
</div>
</form>
<!-- Page -->
<nav th:replace="fragments/pagingCommon :: pagination(${pagination}, 'myPost')"></nav>
<myPost.html>
- input - checbox : 내 글 관리에서 체크박스로 삭제할 컨텐츠를 체크하기 위한 체크박스
- th:value를 이용해서 해당 체크박스의 값을 컨텐츠 id로 지정
- name을 컨트롤러의 파라미터와 똑같은 이름으로 설정
<body>
<nav aria-label="Page navigation example" th:fragment="pagination(pagination, menu)">
<ul class="pagination justify-content-center">
<th:block th:with="start = ${pagination.startPage}, end = ${pagination.endPage}">
<li class="page-item" th:classappend="${pagination.prev == false} ? 'disabled'">
<a class="page-link" th:href="@{'/board/' + ${menu}(page=${start - 1}, range=${pagination.range - 1},
searchText=${param.searchText})}">Previous</a>
</li>
<li class="page-item" th:classappend="${i == pagination.page} ? 'disabled'" th:if="${i != 0}"
th:each="i : ${#numbers.sequence(start, end)}"><a class="page-link" href="#" th:href="@{'/board/' + ${menu}(page=${i},
range=${pagination.range}, searchText=${param.searchText})}" th:text="${i}">1</a></li>
<li class="page-item" th:classappend="${pagination.next == false} ? 'disabled'">
<a class="page-link" th:href="@{'/board/'+ ${menu}(page=${end + 1}, range=${pagination.range + 1},
searchText=${param.searchText})}" href="#">Next</a>
</li>
</th:block>
</ul>
</nav>
</body>
<pagingCommon.html>
- th:fragment : 위 코드들에서 th:replace 붙은 곳에 보여짐
- th:block - th:with : 새로운 변수 초기화 후 사용
- https://black-mint.tistory.com/19의 4번에 정리
[Spring Boot] Thymeleaf 정리
스프링부트에서 타임리프로 프로젝트를 진행 중 2번 이상 다시 찾아본 개념에 대해 정리해놓으려 합니다. 기본 타임리프 문서 : https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#standard-htmlx..
black-mint.tistory.com
'Web > SpringBoot' 카테고리의 다른 글
[Spring Boot] form 양식 빈값 제출 (500 error 해결) (0) | 2021.12.30 |
---|---|
[Spring Boot] 컨트롤러에서 redirect 경로 (0) | 2021.12.30 |
[Spring Boot] Timestamp로 회원가입 시간 저장 (MySQL) (0) | 2021.12.22 |
[Spring Boot] MyBatis + MySQL 셋팅 (+동적 쿼리) (0) | 2021.12.21 |
[Spring Boot] JPA 복합키에 따른 repository (0) | 2021.12.20 |