Java/Spring Boot

Spring Boot 기초(paging) - AWS 풀스택 과정 78일차

awspspgh 2024. 11. 15. 09:30
목차
1. paging

 

1. paging

BoardController.java

package com.ezen.spring.controller;

import com.ezen.spring.domain.BoardVO;
import com.ezen.spring.domain.PagingVO;
import com.ezen.spring.handler.PagingHandler;
import com.ezen.spring.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@RequestMapping("/board/*")
@RequiredArgsConstructor
@Slf4j
@Controller
public class BoardController {
    private final BoardService bsv;

    @GetMapping("/register")
    public String register(){
        return"/board/register";
    }

    @PostMapping("/register")
    public String register(BoardVO boardVO){
        log.info(">>>> boardVO >>>> {}", boardVO);
        int isOk = bsv.register(boardVO);

        return "index";
    }

    @GetMapping("/list")
    public String list(Model m, PagingVO pgvo){
        // 전체 게시글 수 가져오기
        int totalCount = bsv.getTotalCount(pgvo);
        PagingHandler ph = new PagingHandler(pgvo, totalCount);
        m.addAttribute("list", bsv.getList(pgvo));
        m.addAttribute("ph", ph);
        return"/board/list";
    }

    @GetMapping("/detail")
    public String detail(@RequestParam("bno") long bno, Model m){
        BoardVO bvo = bsv.getDetail(bno);
        m.addAttribute("bvo", bvo);
        return "/board/detail";
    }

    @PostMapping("/modify")
    public String modify(BoardVO bvo, RedirectAttributes redirectAttributes){
        int isOk = bsv.update(bvo);
        redirectAttributes.addAttribute("bno", bvo.getBno());
        return "redirect:/board/detail";
    }

    @GetMapping("/delete")
    public String delete(@RequestParam("bno") long bno){
        int isOk = bsv.delete(bno);
        return "redirect:/board/list";
    }
}

 

 BoardService.java

package com.ezen.spring.service;

import com.ezen.spring.domain.BoardVO;
import com.ezen.spring.domain.PagingVO;

import java.util.List;

public interface BoardService {
    int register(BoardVO boardVO);

    List<BoardVO> getList(PagingVO pgvo);

    BoardVO getDetail(long bno);

    int update(BoardVO bvo);

    int delete(long bno);

    int getTotalCount(PagingVO pgvo);
}

 

 BoardServiceImpl.java

package com.ezen.spring.service;

import com.ezen.spring.domain.BoardVO;
import com.ezen.spring.domain.PagingVO;
import com.ezen.spring.repository.BoardMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Service
public class BoardServiceImpl implements  BoardService{
    private final BoardMapper boardMapper;

    @Override
    public int register(BoardVO boardVO) {
        return boardMapper.register(boardVO);
    }

    @Override
    public List<BoardVO> getList(PagingVO pgvo) {
        return boardMapper.getList(pgvo);
    }

    @Override
    public BoardVO getDetail(long bno) {
        return boardMapper.getDetail(bno);
    }

    @Override
    public int update(BoardVO bvo) {
        return boardMapper.update(bvo);
    }

    @Override
    public int delete(long bno) {
        return boardMapper.delete(bno);
    }

    @Override
    public int getTotalCount(PagingVO pgvo) {
        return boardMapper.getTotalCount(pgvo);
    }
}

 

 BoardMapper.java

package com.ezen.spring.repository;

import com.ezen.spring.domain.BoardVO;
import com.ezen.spring.domain.PagingVO;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface BoardMapper {
    int register(BoardVO boardVO);

    List<BoardVO> getList(PagingVO pgvo);

    BoardVO getDetail(long bno);

    int update(BoardVO bvo);

    int delete(long bno);

    int getTotalCount(PagingVO pgvo);
}

 

 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="com.ezen.spring.repository.BoardMapper">
    <insert id="register">
        insert into board(title, writer, content)
        values(#{title}, #{writer}, #{content})
    </insert>
    <select id="getList" resultType="com.ezen.spring.domain.BoardVO">
        select * from board 
        <include refid="search"></include>
        order by bno desc
        limit #{startIndex}, #{qty}
    </select>
    <select id="getDetail" resultType="com.ezen.spring.domain.BoardVO">
        select * from board where bno = #{bno}
    </select>
    <update id="update">
        update board set title = #{title}, content = #{content}, reg_date = now()
        where bno = #{bno}
    </update>
    <delete id="delete">
        delete from board where bno = #{bno}
    </delete>
    <select id="getTotalCount" resultType="int">
        select count(bno) from board
        <include refid="search"></include>
    </select>
    <sql id="search">
        <if test="type != null">
            <trim prefix="where (" suffix=")" suffixOverrides="or">
                <foreach collection="typeToArray" item="type">
                    <trim suffix="or">
                        <choose>
                            <when test="type=='t'.toString()">
                                title like concat('%', #{keyword}, '%')
                            </when>
                            <when test="type=='w'.toString()">
                                title like concat('%', #{keyword}, '%')
                            </when>
                            <when test="type=='c'.toString()">
                                title like concat('%', #{keyword}, '%')
                            </when>
                        </choose>
                    </trim>
                </foreach>
            </trim>
        </if>
    </sql>
</mapper>

 

 PagingVO.java

package com.ezen.spring.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@AllArgsConstructor
public class PagingVO {
    private int pageNo; // 현재 페이지 번호
    private int qty; // 한 페이지에 출력되는 게시글 개수

    private String type;
    private String keyword;


    public PagingVO(){
        pageNo = 1;
        qty = 10;
    }

    public PagingVO(int pageNo, int qty){
        this.pageNo = pageNo;
        this.qty = qty;
    }

    public int getStartIndex(){
        return(this.pageNo - 1) * this.qty;

    }

    public String[] getTypeToArray(){
        return this.type == null ? new String[]{} : this.type.split("");
    }

}

 

 PagingHandler.java

package com.ezen.spring.handler;

import com.ezen.spring.domain.PagingVO;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class PagingHandler {
    private int startPage;
    private int endPage;
    private int realEndPage;
    private boolean prev, next;

    private int totalCount;
    private PagingVO pgvo;

    public PagingHandler(PagingVO pgvo, int totalCount){
        this.pgvo = pgvo;
        this.totalCount = totalCount;

        // 1~10 => 10 / 11~20 => 20 / 21~30 => 30
        // (현재 페이지번호 / 10) 올림 => * 10
        this.endPage = (int)Math.ceil(pgvo.getPageNo() / 10.0)*10;
        this.startPage = endPage - 9;

        this.realEndPage = (int)Math.ceil(totalCount / (double)pgvo.getQty());

        if(realEndPage < endPage){
            this.endPage = realEndPage;
        }

        this.prev = this.startPage > 1; // 1 11 21
        this.next = this.endPage < realEndPage;
    }

}

 

 list.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">

<div layout:fragment="content" class="container-md">
  <h1>Boot List page</h1>
  <hr>
    <!-- search line -->
    <div class="container-fluid">
        <form action="/board/list" method="get" class="d-flex" role="search">
            <select class="form-select" name="type" id="inputGroupSelect01">
                <option th:selected="${ph.pgvo.type == null ? 'true' : 'false' }">Choose...</option>
                <option th:value="t" th:selected="${ph.pgvo.type == 't' ? 'true' : 'false' }">title</option>
                <option th:value="w" th:selected="${ph.pgvo.type == 'w' ? 'true' : 'false' }">writer</option>
                <option th:value="c" th:selected="${ph.pgvo.type == 'c' ? 'true' : 'false' }">content</option>
                <option th:value="tw" th:selected="${ph.pgvo.type == 'tw' ? 'true' : 'false' }">title + writer</option>
                <option th:value="wc" th:selected="${ph.pgvo.type == 'wc' ? 'true' : 'false' }">writer + content</option>
                <option th:value="tc" th:selected="${ph.pgvo.type == 'tc' ? 'true' : 'false' }">content + title</option>
                <option th:value="twc" th:selected="${ph.pgvo.type == 'twc' ? 'true' : 'false' }">all</option>
            </select>
            <input class="form-control me-2" name="keyword" type="text" placeholder="Search" th:value="${ph.pgvo.keyword}" aria-label="Search">
            <input type="hidden" name="pageNo" th:value="1">
            <input type="hidden" name="qty" th:value="${ph.pgvo.qty }">
            <button type="submit" class="btn btn-primary position-relative">
                search
                <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
		    [[${ph.totalCount}]]
		  </span>
            </button>
        </form>
    </div>
  <table class="table table-hover">
      <thead>
        <tr>
          <th>#</th>
          <th>title</th>
          <th>writer</th>
          <th>regDate</th>
        </tr>
      </thead>
      <tbody>
        <tr th:each="bvo:${list}">
            <td>[[${bvo.bno}]]</td>
            <td><a th:href="@{/board/detail(bno=${bvo.bno})}">[[${bvo.title}]]</a></td>
            <td>[[${bvo.writer}]]</td>
          <td>[[${bvo.regDate}]]</td>
        </tr>
      </tbody>
  </table>
    [[${ph}]]
    <!-- ${ } => jsp EL 방식 => 타임리프 th: 사용-->
    <!-- th:classappend : 동적 클래스 추가 -->
    <!-- 페이지네이션 라인 -->
    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${ph.prev eq false ? 'disabled' : ''}">
                <a class="page-link" th:href="@{/board/list(pageNo=${ph.startPage-1}, qty=10, type=${ph.pgvo.type}, keyword=${ph.pgvo.keyword})}" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>

            <!-- ${#numbers.sequence(from,to)} -->
            <th:block th:each="i:${#numbers.sequence(ph.startPage, ph.endPage)}">
            <li class="page-item" th:classappend="${ph.pgvo.pageNo eq i ? 'active' : ''}">
                <a class="page-link" th:href="@{/board/list(pageNo=${i}, qty=10, type=${ph.pgvo.type}, keyword=${ph.pgvo.keyword})}">[[${i}]]</a>
            </li>
            </th:block>

            <li class="page-item" th:classappend="${ph.next eq false ? 'disabled' : ''}">
                <a class="page-link" th:href="@{/board/list(pageNo=${ph.endPage+1}, qty=10, type=${ph.pgvo.type}, keyword=${ph.pgvo.keyword})}" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </ul>
    </nav>

</div>

 

 detail.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">

<div layout:fragment="content" class="container-md">
    <h1>Boot Detail page</h1>
    <hr>
    <form action="/board/modify" method="post" id="modForm">
        <div class="mb-3">
            <label for="r" class="form-label">Created At</label>
            <input type="text" class="form-control" name="regDate" id="r" th:value="${bvo.regDate}" readonly>
        </div><div class="mb-3">
            <input type="hidden" class="form-control" name="bno" id="n" th:value="${bvo.bno}">
        </div>
        <div class="mb-3">
            <label for="title" class="form-label">Title</label>
            <input type="text" class="form-control" name="title" id="title" th:value="${bvo.title}" readonly>
        </div>
        <div class="mb-3">
            <label for="writer" class="form-label">Writer</label>
            <input type="text" class="form-control" name="writer" id="writer" th:value="${bvo.writer}" readonly>
        </div>
        <div class="mb-3">
            <label for="content" class="form-label">Content</label>
            <textarea class="form-control" name="content" id="content" rows="3" readonly>[[${bvo.content}]]</textarea>
        </div>
        <button type="button" id="listBtn" class="btn btn-primary">List</button>
        <!-- de-tail page에서 modify 상태로 변경하기 위한 버튼 -->
        <button type="button" id="modBtn" class="btn btn-warning">Modify</button>
        <a th:href="@{/board/delete(bno=${bvo.bno})}">
            <button type="button" id="delBtn" class="btn btn-danger">Delete</button>
        </a>
    </form>

    <script th:src="@{/js/boardDetail.js}"></script>

</div>

 

 boardDetail.js

console.log("boardDetail.js in!!");

document.getElementById('listBtn').addEventListener('click', () => {
    // 리스트로 이동
    location.href="/board/list";
});

document.getElementById('modBtn').addEventListener('click', ()=>{
// title, content의 readonly를 해지 readOnly = true / false
    document.getElementById('title').readOnly = false;
    document.getElementById('content').readOnly = false;

    // modBtn delBtn 삭제
    document.getElementById('modBtn').remove();
    document.getElementById('delBtn').remove();

    // modBtn => submit
    // <button></button>
    let modBtn = document.createElement('button');
    // <button type="submit"></button> 
    modBtn.setAttribute('type','submit'); 
    // class="btn btn-warning"
    modBtn.classList.add('btn','btn-outline-warning');
    // <button type="submit" class="btn btn-outline-warning">submit</button>
    modBtn.innerText="submit";

    // form 태그의 자식 요소로 추가 - form에 가장 마지막에 추가
    document.getElementById('modForm').appendChild(modBtn);

});

 

출력