728x90

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

list 화면

 

            <!-- 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>

myPost 화면

  • 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

 

+ Recent posts