스프링부트(Spring Boot) MyBatis 게시판 만들기2 - Pagination 추가 [Mysql, DBeaver, IntelliJ, Maven]

예림·2024년 6월 7일
2
post-thumbnail
  • 참고자료

저번 포스팅에서 구현한 게시판 목록에 페이지네이션 기능을 추가해봤다.

수정된 코드 아래에 첨부

게시판 페이지네이션


개발 환경

  • 언어: java

  • Spring Boot ver : 2.3.1.RELEASE

  • Mybatis : 2.3.0

  • IDE: intelliJ

  • SDK: JDK 17

  • 의존성 관리툴: Maven

  • DB: MySQL 8.0.12

  • PaginateDto.java Class 추가

DTO


  • Board.java
    package org.study.board.dto;
    
    import lombok.Data;
    
    import java.util.Date;
    import java.util.List;
    
    @Data
    public class Board {
    
        private Integer bno;
        private String title;
        private String content;
        private String writer;
        private Date regdate;
        private int hit;
        private boolean deleteYn;
        private boolean noticeYn;
        private Integer userIdx;
    
        /*페이징 기능 추가*/
        private Integer pageNo;	    // 현재 페이지 번호
        private int pageSize;	    // 페이지 당 항목 수
        private int pageOffset; 	// 현재 페이지 이전 항목 수
    
        /*첨부파일*/
        private List<FileDto> list;
        private String filename;
        private String[] uuids;
        private String[] filenames;
        private String[] contentTypes;
    
    }
    
  • PaginationDto.java
    package org.study.board.dto;
    
    import lombok.Getter;
    
    import java.io.Serializable;
    
    @Getter
    public class PaginateDto implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private int totalSize;				// 총 항목 수
        private int totalPage;				// 총 페이지 수
    
        private int pageNo = 1;				// 현재 페이지 번호
        private int pageSize;				// 페이지 당 항목 수
        private String pageName = "page";	// 페이지 파라미터명
    
        private boolean pageLoop = false;	// 페이지 순환 여부(pageNo가 totalPage보다 클 경우 pageNo 설정 방법)
    
        private int pageOffset;				// 현재 페이지 이전 항목 수
        private int pageFinal;				// 현재 페이지 마지막 항목 수
    
        private int nationSize;				// 페이지 네비게이션 묶음 당 수
        private int nationBegin;			// 페이지 네비게이션 시작 번호
        private int nationClose;			// 페이지 네비게이션 끝 번호
    
        private String params;				// 파라미터(key1=value1&key2=value2)
    
        public PaginateDto(int pageSize, int nationSize) {
            this.pageSize = pageSize;
            this.nationSize = nationSize;
        }
    
        public PaginateDto(int pageSize) {
            this(pageSize, 0);
        }
    
        public void setPageNo(int pageNo) {
            this.pageNo = pageNo;
        }
    
        public void setPageLoop(boolean pageLoop) {
            this.pageLoop = pageLoop;
        }
    
        public void setTotalSize(int totalSize) {
            this.totalSize = totalSize;
    
            this._calc();
        }
    
        public void setPageName(String pageName) {
            this.pageName = pageName;
        }
    
        public void setParams(String params) {
            this.params = params;
        }
    
        private void _calc() {
            if (pageSize <= 0) return;
    
            // 전체 페이지 수 계산
            totalPage = (int) Math.ceil(totalSize / (double) pageSize);
            if (totalPage < 1) {
                totalPage = 1;
            }
    
            // 현재 페이지 번호 확인
            if (pageNo < 1) {
                pageNo = 1;
            }
            if (totalPage < pageNo) {
                pageNo = (pageLoop ? 1 : totalPage);
            }
    
            // 페이지 번호 계산
            pageOffset = (pageNo - 1) * pageSize;
            pageFinal = pageOffset + pageSize;
            if (pageFinal > totalSize) {
                pageFinal = totalSize;
            }
    
            // 페이지 네비게이션 계산
            if (nationSize > 0) {
                int share = (int) Math.floor(pageNo / (double) nationSize);
    
                nationBegin = (share * nationSize) + 1;
                nationClose = (share + 1) * nationSize;
    
                if (nationClose > totalPage) {
                    nationClose = totalPage;
                }
                if (pageNo % nationSize == 0) {
                    nationBegin -= nationSize;
                    nationClose = nationBegin + nationSize - 1;
                }
            }
        }
    
        @Override
        public String toString() {
            return "PaginateDto [totalSize=" + totalSize + ", totalPage=" + totalPage + ", pageNo=" + pageNo + ", pageSize="
                    + pageSize + ", pageOffset=" + pageOffset + ", pageFinal=" + pageFinal + ", nationSize=" + nationSize
                    + ", nationBegin=" + nationBegin + ", nationClose=" + nationClose + "]";
        }
    
    }
    

Board


  • BoardController.java
    package org.study.board.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.FileUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.Resource;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    import org.study.board.dto.Board;
    import org.study.board.dto.FileDto;
    import org.study.board.dto.PaginateDto;
    import org.study.board.dto.User;
    import org.study.board.repository.BoardMapper;
    import org.study.board.repository.UserMapper;
    import org.study.board.service.BoardService;
    import org.study.board.util.FileUtil;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    import java.util.Map;
    import java.util.UUID;
    
    @Slf4j
    @Controller
    public class BoardController {
    
        @Autowired
        private BoardService boardService;
        @Autowired
        private UserMapper userMapper;
        @Autowired
        private HttpServletRequest request;
    
        @RequestMapping("/main")
        public String main(Board board, Model model, @RequestParam(defaultValue = "1") int page){
    
            // 게시글 총 개수
            int total = boardService.cntBoard();
            model.addAttribute("cntBoard", total);
            // 페이징
            PaginateDto paginate = new PaginateDto(5, 3);
            paginate.setPageNo(page);
            paginate.setTotalSize(total);
    
            board.setPageNo(page);
            board.setPageSize(paginate.getPageSize());
            board.setPageOffset(paginate.getPageOffset());
    
            model.addAttribute("paginate", paginate);
            model.addAttribute("board", boardService.getBoardlist(board));
            return "board/main";
        }
    
        @GetMapping("/board/{bno}")
        public String boardDetail(@PathVariable Integer bno, Model model, Board board){
            Board boardDetail = boardService.getBoard(bno);
            List<FileDto> file = boardService.getFile(board);
    
            model.addAttribute("board", boardDetail);
            model.addAttribute("getFile", file);
            return "board/write";
        }
    
        @RequestMapping("/write")
        public String write(@CookieValue(name="idx", required = false) Long idx, Model model, Board board){
            User loginUser=userMapper.findById(idx);
            model.addAttribute("user", loginUser);
            if(board.getBno()==null){
                model.addAttribute("getBoard", board);
                model.addAttribute("getFile", boardService.getFile(board));
            }
            return "board/write";
        }
    
        @RequestMapping("/insertBoard")
        public String insertBoard(@ModelAttribute Board board, @CookieValue(name="idx", required = false) Long idx, Model model) {
            User loginUser=userMapper.findById(idx);
            board.setWriter(loginUser.getUsername());
            model.addAttribute("user", loginUser);
            boardService.insertBoard(board);
            return "redirect:/main";
        }
    
        @DeleteMapping("/delete/{bno}")
        public ResponseEntity<String> deleteBoard(@PathVariable Integer bno) {
            boolean deleted = boardService.deleteBoard(bno);
            if (deleted) {
                return ResponseEntity.ok("게시물이 성공적으로 삭제되었습니다.");
            } else {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                        .body("게시물 삭제 중에 오류가 발생했습니다.");
            }
        }
    
        /*ajax로 첨부파일 처리*/
        @RequestMapping("/ajaxFile")
        @ResponseBody
        public List<FileDto> ajaxFile(@RequestParam("files") MultipartFile[] uploadFile) {
            // 파일 등록
            List<FileDto> fileList = FileUtil.uploadFile(uploadFile);
            return fileList;
        }
    
        /*파일 다운로드*/
        @RequestMapping("/downloadFile")
        public ResponseEntity<Resource> downloadFile(@ModelAttribute FileDto fileDto) throws IOException {
            return boardService.downloadFile(fileDto);
        }
    
    }
    
  • BoardService.java
    package org.study.board.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.InputStreamResource;
    import org.springframework.core.io.Resource;
    import org.springframework.http.ContentDisposition;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    import org.study.board.dto.Board;
    import org.study.board.dto.FileDto;
    import org.study.board.repository.BoardMapper;
    
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    @Slf4j
    @Service
    public class BoardService {
    
        @Autowired
        private BoardMapper mapper;
    
        public List<Board> getBoardlist(Board board) {
            return mapper.getBoardList(board);
        }
    
        // 첨부파일 리스트
        public List<FileDto> getFile (Board board) {return mapper.getFile(board);}
    
        public Board getBoard(Integer bno){
            return mapper.getBoard(bno);
        }
    
        public Integer cntBoard() {return mapper.cntBoard();}
    
        public void insertBoard(Board board){
            if (board.getBno() != null) {
                mapper.deleteFile(board);
            }
            if(board.getBno()!=null){
                mapper.updateBoard(board);
            }else{
                mapper.insertBoard(board);
            }
            // 파일 이름 유니크하게 생성
            List<FileDto> list = new ArrayList<>();
            String[] uuids = board.getUuids();
            String[] fileNames = board.getFilenames();
            String[] contentTypes = board.getContentTypes();
    
            if(uuids!=null){
                for(int i=0;i<uuids.length;i++){
                    FileDto fileDto = new FileDto();
                    fileDto.setFilename(fileNames[i]);
                    fileDto.setUuid(uuids[i]);
                    fileDto.setContentType(contentTypes[i]);
                    list.add(fileDto);
                }
            }
    
            // 첨부파일 등록 (게시글이 있을경우)
            if (!list.isEmpty()) {
                board.setList(list);
                mapper.insertFile(board);
            }
        }
    
        // 파일 다운로드
        public ResponseEntity<Resource> downloadFile(FileDto fileDto) throws IOException {
            // 파일 저장 경로 설정
            String filePath = "d:\\image";
            Path path = Paths.get(filePath + "/" + fileDto.getUuid() + "_" + fileDto.getFilename());
            String contentType = Files.probeContentType(path);
            // header를 통해서 다운로드 되는 파일의 정보를 설정한다.
            HttpHeaders headers = new HttpHeaders();
            headers.setContentDisposition(ContentDisposition.builder("attachment")
                    .filename(fileDto.getFilename(), StandardCharsets.UTF_8)
                    .build());
            headers.add(HttpHeaders.CONTENT_TYPE, contentType);
    
            Resource resource = new InputStreamResource(Files.newInputStream(path));
    
            return new ResponseEntity<>(resource, headers, HttpStatus.OK);
        }
    
        public boolean deleteBoard(Integer bno){
            return mapper.deleteBoard(bno);
        }
    
    }
    
  • BoardMapper.java
    package org.study.board.repository;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.study.board.dto.Board;
    import org.study.board.dto.FileDto;
    
    import java.util.List;
    
    @Mapper
    public interface BoardMapper {
    
        List<Board> getBoardList(Board board);
        List<FileDto> getFile(Board board);
        Board getBoard(Integer bno);
        Integer cntBoard();
        void insertBoard(Board board);
        void updateBoard(Board board);
        boolean deleteBoard(Integer bno);
        void insertFile(Board board);
        void deleteFile(Board board);
    }
    
  • 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="org.study.board.repository.BoardMapper">
    
        <select id="getBoardList" resultType="org.study.board.dto.Board">
            /*SELECT *
            from board
            where delete_yn!=1*/
            SELECT b.*
                 , CASE WHEN COUNT(*)  <![CDATA[>]]> 1
                            THEN CONCAT(f.filename, '외 ' , COUNT(*)-1, '건')
                        ELSE f.filename
                END file_name
            FROM board b
            LEFT JOIN file f ON b.bno = f.bno
            WHERE b.delete_yn != 1
            GROUP BY b.bno
            ORDER BY b.bno DESC
                LIMIT #{pageSize} OFFSET ${pageOffset}
        </select>
    
        <select id="getBoard" parameterType="int"
                resultType="org.study.board.dto.Board">
            select *
            from board
            where delete_yn!=1 and bno=#{bno}
        </select>
    
        <select id="cntBoard" resultType="Integer">
            SELECT count(*)
            FROM board
            WHERE delete_yn!=1
        </select>
    
        <!--첨부파일 리스트-->
        <select id="getFile" resultType="org.study.board.dto.FileDto">
            SELECT *
            FROM file
            WHERE delete_yn!=1 and bno=#{bno}
        </select>
    
        <insert id="insertBoard" parameterType="org.study.board.dto.Board">
            INSERT INTO board (title, content, writer, regdate, hit, delete_yn, user_idx)
            VALUES (#{title}, #{content}, #{writer}, NOW(), 0, 0, #{userIdx})
            <selectKey keyProperty="bno" resultType="int" order="AFTER">
                SELECT LAST_INSERT_ID()
            </selectKey>
        </insert>
    
        <update id="updateBoard" parameterType="org.study.board.dto.Board">
            UPDATE board
            SET title=#{title}, content=#{content},regdate=NOW(), hit = #{hit}, delete_yn=0, user_idx = #{userIdx}
            WHERE bno = #{bno}
        </update>
    
        <update id="deleteBoard" parameterType="int">
            update board
            SET delete_yn=1
            WHERE bno=#{bno}
        </update>
    
        <!-- 파일 정보를 삽입하는 쿼리 -->
        <insert id="insertFile" parameterType="org.study.board.dto.Board">
            INSERT INTO file (bno, uuid, filename, content_type)
            VALUES
            <foreach collection="list" separator="," index="index" item="item">
                (#{bno}, #{item.uuid}, #{item.filename}, #{item.contentType})
            </foreach>
        </insert>
    
        <update id="deleteFile" parameterType="org.study.board.dto.Board">
            UPDATE file
            SET delete_yn=1
            WHERE bno=#{bno} AND delete_yn=0
        </update>
    
    </mapper>

View (webapp/WEB-INF/jsp/views)


  • board
    • main.jsp
      • 게시판 메인 페이지

        <%@ page contentType="text/html;charset=UTF-8" language="java" %>
        <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
        <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
        <!DOCTYPE HTML>
        <head>
            <title>게시판 메인</title>
            <style>
                .button-container {
                    margin-top: 20px;
                }
                .button-container input {
                    margin-right: 10px;
                }
                table {
                    width: 100%;
                    border-collapse: collapse;
                }
                th, td {
                    border: 1px solid #ddd;
                    padding: 8px;
                }
                th {
                    background-color: #f2f2f2;
                }
            </style>
        </head>
        <body>
        <h1>게시글 목록</h1>
        게시판 총 ${cntBoard}<table>
            <thead>
            <tr>
                <th class="one wide">번호</th>
                <th class="ten wide">글제목</th>
                <th class="two wide">작성자</th>
                <th class="three wide">작성일</th>
            </tr>
            </thead>
        
            <tbody>
            <c:forEach var="board" items="${board}">
                <tr>
                    <td><span>${board.bno}</span></td>
                    <td><a href="/board/${board.bno}"><span>${board.title}</span></a></td>
                    <td><span>${board.writer}</span></td>
                    <td><span>${board.regdate}</span></td>
                </tr>
            </c:forEach>
            </tbody>
        </table>
        <%--페이징 처리 시작--%>
        <div class="col-sm-12 col-md-7" style="margin: auto">
            <div class="dataTables_paginate paging_simple_numbers" id="dataTable_paginate">
                <div class="paging">
                    <c:set var="_paginate" value="${paginate}" />
                    <c:if test="${not empty _paginate}">
                        <c:set var="_page_no" value="${_paginate.pageNo}" />
                        <c:set var="_page_name" value="${_paginate.pageName}" />
                        <c:set var="_page_total" value="${_paginate.totalPage}" />
                        <c:set var="_page_params" value="${fn:escapeXml(_paginate.params)}${empty _paginate.params ? '' : '&'}" />
                        <c:set var="_nation_size" value="${_paginate.nationSize}" />
                        <c:set var="_nation_begin" value="${_paginate.nationBegin}" />
                        <c:set var="_nation_close" value="${_paginate.nationClose}" />
                        <c:set var="_paging" value="" />
        
                        <%-- c:choose>
                            <c:when test="${_page_no gt 1}">
                                <c:set var="_paging">${_paging}<a href="?${_page_params}${_page_name}=1" page="1" class="first">처음</a></c:set>
                            </c:when>
                            <c:otherwise>a href="#" onclick="re
                        <c:set var="_paging">${_paging}<turn false" class="first">처음</a></c:set>
                            </c:otherwise>
                        </c:choose --%>
        
                        <c:choose>
                            <c:when test="${_nation_begin gt 1}">
                                <c:set var="_page_prev" value="${_nation_begin - 1}" />
                                <c:set var="_paging">${_paging}<a href="?${_page_params}${_page_name}=${_page_prev}" page="${_page_prev}" class="prev">이전</a></c:set>
                            </c:when>
                            <c:otherwise>
                                <c:set var="_paging">${_paging}<a href="#" onclick="return false" class="prev">이전</a></c:set>
                            </c:otherwise>
                        </c:choose>
        
                        <c:forEach var="__p" begin="${_nation_begin}" end="${_nation_close}">
                            <c:choose>
                                <c:when test="${__p eq _page_no}">
                                    <c:set var="_paging">${_paging}<strong class="current">${__p}</strong></c:set>
                                </c:when>
                                <c:otherwise>
                                    <c:set var="_paging">${_paging}<a href="?${_page_params}${_page_name}=${__p}" page="${__p}">${__p}</a></c:set>
                                </c:otherwise>
                            </c:choose>
                        </c:forEach>
        
                        <c:choose>
                            <c:when test="${_nation_close ne _page_total}">
                                <c:set var="_page_next" value="${_nation_begin + _nation_size}" />
                                <c:set var="_paging">${_paging}<a href="?${_page_params}${_page_name}=${_page_next}" page="${_page_next}" class="next">다음</a></c:set>
                            </c:when>
                            <c:otherwise>
                                <c:set var="_paging">${_paging}<a href="#" onclick="return false" class="next">다음</a></c:set>
                            </c:otherwise>
                        </c:choose>
        
                        <%-- c:choose>
                            <c:when test="${_page_no lt _page_total}">
                                <c:set var="_paging">${_paging}<a href="?${_page_params}${_page_name}=${_page_total}" page="${_page_total}" class="last">마지막</a></c:set>
                            </c:when>
                            <c:otherwise>
                                <c:set var="_paging">${_paging}<a href="#" onclick="return false" class="last">마지막</a></c:set>
                            </c:otherwise>
                        </c:choose --%>
        
                        <c:out value="${_paging}" escapeXml="false" />
                    </c:if>
                </div>
            </div>
        </div>
        <%--페이징 처리 끝--%>
            <%
            // 쿠키 배열 가져오기
            Cookie[] cookies = request.getCookies();
            boolean isLoggedIn = false;
        
            // 쿠키 배열을 순회하며 idx 쿠키의 존재 여부 확인
            if (cookies != null) {
                for (Cookie c : cookies) {
                    if (c.getName().equals("idx")) {
                        // idx 쿠키가 존재하면 로그인 상태로 설정
                        isLoggedIn = true;
                        break; // 로그인 상태이므로 더 이상 반복할 필요가 없음
                    }
                }
            }
        %>
        
        <div class="button-container">
            <%
                // 로그인 상태에 따라 버튼 표시
                if (isLoggedIn) {
            %>
            <input type="button" value="user 목록" onclick="location.href='/user/main'"><br/><br/>
            <input type="button" value="글 작성" onclick="location.href='/write'"><br/><br/>
            <input type="button" value="로그아웃" onclick="location.href='/logout'"><br/><br/>
            <%
            } else {
            %>
            <input type="button" value="로그인" onclick="location.href='/login'"><br/><br/>
            <input type="button" value="회원가입" onclick="location.href='/join'"><br/><br/>
            <%
                }
            %>
        </div>
        
        </body>
        </html>
        

실행 화면


  • /main
    • 메인 페이지 - 1페이지

    • 메인 페이지 - 2페이지 /main?page=2

profile
백엔드 개발하는 사람

0개의 댓글