IT/development

[springBoot] ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ(feat. MariaDB)

์•Œ ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž 2022. 11. 19.

๋ชฉ์ฐจ

    ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ ๊ด€๋ จํ•ด์„œ ๋ฏธ๋ž˜์˜ ๋‚ด๊ฐ€ ๋ณด๊ธฐ ์œ„ํ•ด ๊ธฐ๋ก ๐Ÿ˜ƒ

    ์›น๊ฐœ๋ฐœ์˜ ๊ธฐ์ดˆ ์ค‘ ๊ธฐ์ดˆ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€๋„ค์ด์…˜(ํŽ˜์ด์ง•) ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ ๊ฐ„๋žตํ•˜๊ฒŒ ์ •๋ฆฌ

    ๊ทธ๋™์•ˆ์€ ์›๋ฆฌ๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๊ณ  ์ผ๋‹ค๋ฉด ์ด์ œ ํ•ต์‹ฌ์œ„์ฃผ๋กœ ์ž˜ ์ •๋ฆฌํ•ด ๋ณด์ž.

    ์‹œ๊ฐ„ ์ƒ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋Š” ๋””์ž์ธ์€ ์œ„์ฒ˜๋Ÿผ ํ•˜๋‚˜๋„ ์•ˆ ์ด์˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜ ๋‚˜์˜ค๋Š”์ง€ ์œ„์ฃผ๋กœ ์ž‘์„ฑํ•จ

    ๊ฐœ๋ฐœํ™˜๊ฒฝ

    back-end: springBoot 2.6.13(jdk 1.8)/mybatis 3.5.9/h2 database H2 2.1.214 (2022-06-13)(mode๋Š” mySQL๋กœ ์„ค์ •)

    front-end: thymeleaf/javascript/jQuery

     

    ์˜ˆ์‹œ๋ฅผ ์œ„ํ•œ ํ…Œ์ด๋ธ” 

    CREATE TABLE t_board(
                        board_id bigint auto_increment,
                        title varchar (30),
                        content varchar (30),
                        name varchar (30),
                        reg_date timestamp,
                        update_date timestamp,
                        primary key(board_id)
    );

     

    ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ๋„ฃ๋Š” ๊ฒƒ์€ ์ƒ๋žต

    DBMS๋Š” mysql ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ

    ์ฟผ๋ฆฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œํ•œ ๊ฑธ์–ด์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ

    limit์ ˆ ์„ค๋ช…์€ ์ƒ๋žตํ•จ(์‹œ์ž‘๊ฐ’, ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ ๊ฑด์ˆ˜)

     

    ์ฟผ๋ฆฌ๋Š” limit๋งŒ ๊ฑธ์–ด์„œ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.

    ๋‹ค๋งŒ limit์ ˆ ๋’ค์˜ parameter๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญํ•ด์„œ ๋„˜์–ด์˜จ ๊ฐ’๋“ค๋กœ ๋ณ€์ˆ˜ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ

    1ํŽ˜์ด์ง€๋Š” limit 0, 10

    2ํŽ˜์ด์ง€๋Š” limit 10,10

    3ํŽ˜์ด์ง€๋Š” limit 20,10 ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.

    mapper 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">
    <!-- column๊ณผ vo์˜ field๋ช…์ด ์ƒ์ดํ•ด์„œ resultMap ์„ ์–ธ -->        
    <mapper namespace="paging.study.mapper.BoardMapper">
        <resultMap id="boardMap" type="paging.study.domain.vo.BoardVO">
            <id column="board_id" property="boardId"/>
            <result column="title" property="title"/>
            <result column="content" property="content"/>
            <result column="name" property="name"/>
            <result column="reg_date" property="regDate"/>
            <result column="update_date" property="updateDate"/>
        </resultMap>
        <!-- ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์™€ ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜๊ฐ€ ์žˆ๋Š” Criteria๋ฅผ parameter๋กœ ๋ฐ›์•„ limit์ ˆ์— ํ™œ์šฉ -->
        <select id="findBoardListPaging" parameterType="paging.study.domain.Criteria" resultMap="boardMap">
            SELECT * FROM t_board
            ORDER BY reg_date DESC
            limit #{limitStart}, #{amount}
        </select>
    </mapper>

    Criteria(์ฟผ๋ฆฌ์˜ paramer์— ํ™œ์šฉ)

    // ํ”„๋ก ํŠธ์—์„œ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์™€ ํŽ˜์ด์ง€ ์ถœ๋ ฅ ๊ฐœ์ˆ˜๋ฅผ ๋„˜๊ธฐ๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค
    package paging.study.domain;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Getter @Setter
    @ToString
    public class Criteria {
    
        private int pageNum;        // ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ(ํ˜„์žฌ ํŽ˜์ด์ง€๊ฐ€ ๋ช‡ ํŽ˜์ด์ง€์ธ์ง€)
        private int amount;         // ํ•œ ํ™”๋ฉด์— ์ถœ๋ ฅํ•œ ํŽ˜์ด์ง€ ๊ฐœ์ˆ˜
        private int limitStart;             // ์ฟผ๋ฆฌ์—์„œ (pageNum -1) * amount ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜
    	
        // ๊ธฐ๋ณธ ๊ฐ’ ์„ธํŒ…(ํ˜„์žฌ ํŽ˜์ด์ง€๋Š” 1, ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ๋Š” 10๊ฑด์ธ ๊ฒฝ์šฐ)
        public Criteria() {
            this.pageNum = 1;
            this.amount = 10;
        }
    
        public Criteria(int pageNum, int amount) {
            this.pageNum = pageNum;
            this.amount = amount;
        }
        
        // ์ฟผ๋ฆฌ์—์„œ limit 1๋ฒˆ ์งธ parameter๋กœ ์“ธ ๊ฐ’(mybatis์—์„œ getter๋กœ ์ด ๊ฐ’์„ ์‚ฌ์šฉ)
        public int getLimitStart() {
            return this.limitStart = (pageNum - 1) * this.amount;
        }
    }

    BoardVO

    package paging.study.domain.vo;
    
    import lombok.*;
    
    import java.time.LocalDateTime;
    
    @Getter @Setter
    @ToString
    @NoArgsConstructor
    public class BoardVO {
    
        private Long boardId;
        private String title;
        private String content;
        private String name;
        private LocalDateTime regDate;
        private LocalDateTime updateDate;
    
        public BoardVO(String title, String content, String name) {
            this.title = title;
            this.content = content;
            this.name = name;
        }
    }

     

    mapper interface

    package paging.study.mapper;
    
    import org.apache.ibatis.annotations.Mapper;
    import paging.study.domain.Criteria;
    import paging.study.domain.vo.BoardVO;
    
    import java.util.List;
    
    @Mapper
    public interface BoardMapper {
    	
        List<BoardVO> findBoardListPaging(Criteria cri);
        // ์ „์ฒด ์นด์šดํŠธ
        @Select("SELECT COUNT(*) FROM t_board")
        int findBoardCount();
    }

    service

    package paging.study.service;
    
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import paging.study.domain.Criteria;
    import paging.study.domain.vo.BoardVO;
    import paging.study.mapper.BoardMapper;
    
    import java.util.List;
    
    @Service
    @Slf4j
    @RequiredArgsConstructor
    public class BoardService {
    
        private final BoardMapper boardMapper;
    
        public List<BoardVO> findBoardListPaging(Criteria cri) {
            return boardMapper.findBoardListPaging(cri);
        }
        
        public int findBoardCount() {
            return boardMapper.findBoardCount();
        }
    }

    controller

    package paging.study.controller;
    
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    import paging.study.domain.Criteria;
    import paging.study.domain.paging.PageMaker;
    import paging.study.domain.vo.BoardVO;
    import paging.study.service.BoardService;
    import paging.study.service.ReplyService;
    
    import java.util.List;
    
    @Controller
    @Slf4j
    @RequiredArgsConstructor
    public class BoardController {
    
        private final BoardService boardService;
    
        /**
         * content list
         * @param cri
         * @param model
         * @return
         */
        @GetMapping("/board/list")
        public String listPaging(@ModelAttribute("cri") Criteria cri, Model model) {
        	// ํ™”๋ฉด์—์„œ cri๋ฅผ ๋ฐ›์•„์„œ ๋ชฉ๋ก ์กฐํšŒ ์„œ๋น„์Šค ํ˜ธ์ถœ์‹œ parameter๋กœ ๊ฐ™์ด ๋„˜๊น€
            List<BoardVO> list = boardService.findBoardListPaging(cri);
            // ํŽ˜์ด์ง€๋„ค์ด์…˜ ํ™”๋ฉด ์ฒ˜๋ฆฌ์— ํ•„์š”ํ•œ ์ „์ฒด ๋ชฉ๋ก ๊ฐœ์ˆ˜ ๊ตฌํ•จ
            int boardCount = boardService.findBoardCount();
            // model์— 2 ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ(๋ฆฌ์ŠคํŠธ, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ์ดํ„ฐ)๋ฅผ ๋‹ด์•„์„œ view์— ์ „๋‹ฌ
            model.addAttribute("list", list);
            // pageMaker์— 2๊ฐœ์˜ parameter๋ฅผ ๋„˜๊น€
            model.addAttribute("pageMaker", new PageMaker(boardCount, cri));
            return "board/boardList";
        }
    }

     


    ํŽ˜์ด์ง€๋„ค์ด์…˜ ํ™”๋ฉด ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค ๐Ÿ˜Ž

    // ํ”„๋ก ํŠธ์—์„œ ๋„˜๊ธด Criteria๋ฅผ ์ฐธ์กฐํ•ด์„œ ์‹ค์ œ ํ™”๋ฉด์—์„œ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ„์‚ฐ์„ ํ•˜๋Š” ํด๋ž˜์Šค
    package paging.study.domain.paging;
    
    import lombok.Getter;
    import lombok.ToString;
    import paging.study.domain.Criteria;
    
    @Getter	// ์™ธ๋ถ€์—์„œ ๊ฐ’ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ํ•˜๋„๋ก setter๋Š” ์„ ์–ธํ•˜์ง€ ์•Š์Œ
    @ToString
    // ํŽ˜์ด์ง€๋„ค์ด์…˜ ํ™”๋ฉด์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐํ•˜๋Š” ํด๋ž˜์Šค
    public class PageMaker {
        private int startPage;  //ํŽ˜์ด์ง• ํ™”๋ฉด ํ•˜๋‹จ์˜ ์‹œ์ž‘ ๋ฒˆํ˜ธ(5ํŽ˜์ด์ง€๋ผ๊ณ  ํ•˜๋ฉด [1][2][3][4][5] ์—ฌ๊ธฐ์„œ ์ œ์ผ ์ฒซ ๋ฒˆ ์งธ ์‹œ์ž‘๋ฒˆํ˜ธ)
        private int endPage;    //ํŽ˜์ด์ง• ํ™”๋ฉด ํ•˜๋‹จ์˜ ๋ ๋ฒˆํ˜ธ(5ํŽ˜์ด์ง€๋ผ๊ณ  ํ•˜๋ฉด [1][2][3][4][5] ์—ฌ๊ธฐ์„œ ์ œ์ผ ๋ ๋ฒˆํ˜ธ)
        private boolean prev, next;     // ์ด์ „, ๋‹ค์Œ ์กด์žฌ ์—ฌ๋ถ€
    
        private int totalCount;         // ์ „์ฒด ๊ฒŒ์‹œ๊ธ€ ์ˆ˜
        private Criteria cri;      // ํ”„๋ก ํŠธ์—์„œ ์ „๋‹ฌํ•˜๋Š” pageNum(ํ˜„์žฌ ํŽ˜์ด์ง€), amount(์ถœ๋ ฅ ํŽ˜์ด์ง€) ์ „๋‹ฌ ์—ญํ• 
    	
        // ๊ฒŒ์‚ฐ์‹์— ํ•„์š”ํ•œ ๊ฒŒ ์ „์ฒด ์นด์šดํŠธ, ๊ทธ๋ฆฌ๊ณ  ํ˜„์žฌ ํŽ˜์ด์ง€์™€ ํŽ˜์ด์ง€ ๊ฐœ์ˆ˜
        // 2๊ฐœ parameter๋ฅผ ๋ฐ›์•„์„œ ์•„๋ž˜์—์„œ ๊ณ„์‚ฐ ์ฒ˜๋ฆฌ(๊ณต์‹์ด๋‹ˆ ์™ธ์šธ ํ•„์š”๋Š” ์—†๊ณ  ์ด๋Ÿฐ์‹์œผ๋กœ ๊ตฌํ•˜๋Š” ๊ตฌ๋‚˜๋ผ๊ณ  ์ดํ•ดํ•˜๋ฉด ๋จ)
        public PageMaker(int totalCount, Criteria cri) {
            this.totalCount = totalCount;
            this.cri = cri;
    
            this.endPage = (int)(Math.ceil(cri.getPageNum()/10.0)) * 10;
            this.startPage = this.endPage - 9;
            // ์‹ค์ œ ๋ ๋ฒˆํ˜ธ
            int realEnd = (int)(Math.ceil((totalCount * 1.0) / cri.getAmount()));
    		// ๋งŒ์ผ 80๊ฐœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ๋๋ฒˆํ˜ธ๋Š” 10์ด ์•„๋‹ˆ๋ผ 8์ด ๋˜์–ด์•ผ ํ•˜๊ธฐ์— ์‹ค์ œ ๋๋ฒˆํ˜ธ์™€ ๋น„๊ตํ•ด์„œ ์‹ค์ œ ๋ ๋ฒˆํ˜ธ๋กœ ์น˜ํ™˜
            if(realEnd < this.endPage) {
                this.endPage = realEnd;
            }
    		// ์ด์ „, ๋‹ค์Œ
            this.prev = this.startPage > 1;
            this.next = this.endPage < realEn
        }
    }

    view(thymeleaf)

    <!-- ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์˜์—ญ start -->
    <div>
            <table class="table table-striped">
                <thead>
                <tr>
                    <th>์•„์ด๋””</th>
                    <th>์ œ๋ชฉ</th>
                    <th>๋‚ด์šฉ</th>
                    <th>์ด๋ฆ„</th>
                </tr>
                </thead>
                <tbody>
                <!-- ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ๋ฃจํ”„ ๋Œ๋ฉฐ ํ‘œ์‹œ -->
                <tr th:each="item : ${list}">
                    <td th:text="${item.boardId}"></td>
                    <td th:text="${item.title}"></td>
                    <td th:text="${item.content}"></td>
                    <td th:text="${item.name}"></td>
                </tr>
                </tbody>
            </table>
        </div>
    <!-- // ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์˜์—ญ end -->    
    
    <!-- ๊ฒŒ์‹œํŒ ํ•˜๋‹จ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์˜์—ญ start -->
            <nav aria-label="Page navigation">
                <ul class="pagination">
                    <!-- prev -->
                    <li class="page-item" th:if="${pageMaker.prev} == true">
                        <a class="page-link" th:href="@{/board/list(pageNum=${pageMaker.startPage}-1)}">Prev</a>
                    </li>
                    <!-- pageMaker์˜ startPage๋ถ€ํ„ฐ endPage๊นŒ์ง€ ๋ฃจํ”„, aํƒœ๊ทธ์˜ href์— idx๋ฅผ ๋งํฌ(get๋ฐฉ์‹์œผ๋กœ pageNum์„ ๋ถ™์—ฌ์„œ) -->
                    <li class="page-item" id="paginate_btn" th:each="idx: ${#numbers.sequence(pageMaker.startPage, pageMaker.endPage)}" th:classappend="${pageMaker.cri.pageNum} == ${idx} ? active : null">
                        <a class="page-link" th:href="@{/board/list(pageNum=${idx})}" th:text="${idx}"></a>
                    </li>
                    <!-- next -->
                    <li class="page-item" th:if="${pageMaker.next} == true and ${pageMaker.endPage > 0}">
                        <a class="page-link" th:href="@{/board/list(pageNum=${pageMaker.endPage}+1)}">Next</a>
                    </li>
                </ul>
            </nav>
            <!-- // ๊ฒŒ์‹œํŒ ํ•˜๋‹จ์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์˜์—ญ end -->

    ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ชป์ƒ๊ธด ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ๊ฐ€ ๋œ ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋œ๋‹ค.

    ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜ ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ ํ›„ ์•„๋ž˜์ฒ˜๋Ÿผ ๋””์ž์ธ์„ ์ž…ํžˆ๋ฉด ๋œ๋‹ค.

    ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๊ธ‰ํ•˜๊ฒŒ ๋งŒ๋“ ๊ฑฐ๋ผ ์˜คํƒˆ์ž๊ฐ€ ์žˆ๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

     

    ๋Œ“๊ธ€