⚙️ Backend/스프링(Spring) Framework

스프링 - 페이징 화면 처리에 관하여..

코너(Corner) 2021. 5. 13.
반응형

 

스프링 - 페이징 화면 처리

1. 페이징 화면 처리

URL 파라미터로 정상적으로 원하는 페이지 번호를 표시해 사용자가 페이지 번호를 클릭할 수 있게 처리한다.
  • 브라우저 주소창에서 페이지 번호를 전달해 결과 확인하는 단계
  • JSP에서 페이지 번호를 출력하는 단계
  • 각 페이지 번호에 클릭 이벤트 처리
  • 전체 데이터 개수를 반영해 페이지 번호 조절

1-1. 페이징 처리할 때 필요한 정보들

  • 현재 페이지 번호(page)
  • 이전과 다음으로 이동 가능한 링크의 표시 여부(prev,next)
  • 화면에서 보여지는 페이지의 시작 번호와 끝 번호(startPage,endPage)

1-1-1. 끝 페이지 번호와 시작 페이지 번호

사용자 19페이지를 본다면 페이지 번호가 화면에 11~20까지 떠야한다.

페이징의 끝 번호 계산

this.startPage = this.endPage - 9;

끝 번호는 전체 데이터 수에 의해 영향을 받는다.

만일 10개 씩 보여주는 경우 전체 데이터 수가 80이라고 가정하면 끝 번호는 10이 아닌 8이 되어야 한다.

만일 끝 번호와 한 페이지당 출력되는 데이터 수의 곱이 전체 데이터 수보다 크다면 끝 번호는 다시 total을 이용해 계산되면 된다.

total을 이용한 endpage의 재계산

realEnd = (int)(Math.ceil((total*1.0) / amount));
if(realEnd < this.endPage ) {
  this.endPage = realEnd;
}

이전과 다음

이전 prev는 시작 번호가 1보다 큰 경우라면 존재한다.

this.prev = this.startPage > 1;

다음 링크는 realEndrㅏ 끝 번호보다 큰 경우에만 존재한다.

this.next = this.endPage < realEnd;

1-2. 페이징 처리를 위한 클래스 설계

클래스를 구성해 처리하면 편하다. Controller 계층에서 JSP 화면에 전달할 때도 객체를 생성해 Model에 담아 보내는 과정이 단순해진다.

org.zerock.domain 아래 PageDTO 클래스 설계

package com.koreait.domain;

import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
// pageDTO가 Criteria에게 의존하는 것, 생성자에서 cri 없으면 얘는 쓸 수 없기 때문에
public class PageDTO {
   private int startPage;
   private int endPage;
   private int realEnd;
   private boolean next, prev;
   private int total;
   private Criteria cri;

   public PageDTO(Criteria cri, int total) {
      this.cri = cri;
      this.total = total;

      //Math.ceil() : 올림
      this.endPage = (int)(Math.ceil(cri.getPageNum() / 10.0)) * 10;
      this.startPage = endPage - 9;

      this.realEnd = (int)(Math.ceil((total * 1.0) / cri.getAmount()));
      if(realEnd < this.endPage) {
         this.endPage = realEnd;
      }
      this.prev = this.startPage > 1;
      this.next = this.endPage < this.realEnd;
   }
}

BoardController 수정

    @GetMapping("/list")
    public void list(Criteria cri, Model model) {
        log.info("list");
        model.addAttribute("list", service.getList(cri));
        model.addAttribute("pageMaker", new PageDTO(cri, 123));
    }

total에 임의 값을 넣고 테스트 한다.

1-3. JSP에서 페이지 번호 출력

JSTL로 처리한다.

list.jsp 수정

    <div class="big-width" style="text-align:center;">
      <c:if test="${pageMaker.prev}">
        <a class="changePage" href="${1}"><code>&lt;&lt;</code></a>
        <a class="changePage" href="${pageMaker.startPage - 1}"><code>&lt;</code></a>
      </c:if>
      <c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
      	<c:choose>
      		<c:when test="${num eq pageMaker.cri.pageNum}">
		      <code>${num}</code>
     		 </c:when>
         <c:otherwise>
      		<a class="changePage" href="${num}"><code>${num}</code></a>
          </c:otherwise>
		</c:choose>
      </c:forEach>
      <c:if test="${pageMaker.next}">
        <a class="changePage" href="${pageMaker.endPage + 1}"><code>&gt;</code></a>
        <a class="changePage" href="${pageMaker.realEnd}"><code>&gt;&gt;</code></a>
      </c:if>
    </div>

list.jsp에서 form태그를 추가해 URL의 이동을 처리하도록 변경하였다.

<a> 태그를 클릭해 페이지 이동이 없도록 preventDefault 처리를 하고 form 태그 내 pageNum 같은 href 속성값으로 변경한다.

    <form id="actionForm" action="/board/list">
                                                <input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum}">
                                                <input type="hidden" name="amount" value="${pageMaker.cri.amount}">
</form>

1-4. 조회 페이지로 이동

사용자가 3page 게시글을 클릭 후 목록으로 복귀하면 다시 1페이지로 이동된다.

해결하기 위해선 조회 페이지로 갈 때 현재 목록 페이지의 pageNum과 amount를 같이 전달해야 한다.

form 태그에 추가로 게시물의 번호를 같이 전송하고 action 값을 조정해 처리할 수 있다.

페이지 번호는 조회 페이지에 전달되지 않기 때문에, 조회 페이지에서 목록 페이지로 다시 이동할 때 /board/list를 다시 호출하는 것.

<script>
        $(".changePage").on("click", function(e){
            e.preventDefault();
            var actionForm = $("#actionForm");
            var pageNum = $(this).attr("href");
            actionForm.find("input[name='pageNum']").val(pageNum);
            actionForm.submit();
        })

        //alert("${result}");
        var result = "${result}";
        $(document).ready(function(){
            if(result == '' || isNaN(result)){
                return;
            }
            alert("게시글 " + result + "번이 등록되었습니다.")
        })
    </script>

1-4-1. 조회 페이지에서 다시 목록 페이지로 이동 - 페이지 번호 유지

조회 페이지에서 다시 목록 페이지로 이동하기 위한 파라미터들이 같이 전송되었다면 조회 페이지에서 목록으로 이동하기 위한 이벤트를 처리해야 한다. BoardController의 get 메소드는 원래는 게시물의 번호만 받도록 처리되었지만 추가적인 파라미터가 붙으면서 Criteria도 추가해야 한다.

BoardController


//    등록 처리와 유사하게 조회 처리도 BoardController 를 이용해서 처리할 수 있다. 
//    특별한 경우가 아니라면 조회는 GET 방식으로 처리하므로, @GetMapping을 이용한다.
    @GetMapping({"/get", "/modify"})
    /*
     RequestParam은 객체와 일반 변수가 동시에 있을 때 분리하기 위해 작성한다. (좀 더 명시적 처리)
     * */
    public void get(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri,Model model) {
        log.info("/get");
        model.addAttribute("board",service.get(bno));
    }

@ModelAttribute는 자동으로 Model에 데이터를 지정한 이름으로 담는다.

사용하지 않아도 Controller에 화면으로 파라미터가 된 객체가 전달되지만 좀 더 명시적으로 지정하기 위해 사용.

get.jsp

 <h3><a href="/board/list?pageNum=${cri.pageNum}&amount=${cri.amount}" class="button small">목록 보기</a></h3>
                  <div class="content">
                     <div class="form">
                        <form action="/board/remove">
                           <div class="fields">
                              <div class="field">
                                 <h4>번호</h4>
                                 <input name="bno" type="text" value="${board.bno}" readonly/>
                              </div>
                              <div class="field">
                                 <h4>제목</h4>
                                 <input name="title" type="text" value="${board.title}" readonly/>
                              </div>
                              <div class="field">
                                 <h4>내용</h4>
                                 <textarea name="content" rows="6" style="resize:none" readonly>${board.content}</textarea>
                              </div>
                              <div class="field">
                                 <h4>작성자</h4>
                                 <input name="writer" type="text" value="${board.writer}" readonly/>
                              </div>
                           </div>
                           <ul class="actions special">
                              <li>
                                 <input type="button" class="button" value="수정" onclick="location.href='/board/modify?bno=${board.bno}&pageNum=${cri.pageNum}&amount=${cri.amount}'"/>
                                 <input type="submit" class="button" value="삭제"/>
                                 <input type="hidden" name="pageNum" value="${cri.pageNum}">
                                 <input type="hidden" name="amount" value="${cri.amount}">
                              </li>
                           </ul>
                        </form>

1-4-2. 조회 페이지에서 수정/삭제 페이지로 이동

Modify 버튼을 통해 수정/삭제 페이지로 이동한다. BoardController get()메소드 /get과 /modify를 함께 처리하므로 별도의 처리 없이도 Criteria를 Model에 담아 cri라는 이름으로 전달하게 된다.


1-5. 수정과 삭제 처리

modify.jsp에서 form 태그를 이용해 데이터를 처리한다.

거의 입력과 비슷한 방식으로 구현되는데 이제 pageNum과 amount값이 존재하므로 form태그 내에서 같이 전송할 수 있게 수정해야 한다.

modify.jsp

 <div class="content">
                     <div class="form">
                        <form action="/board/modify" method="post" id="modifyForm">
                         <input type="hidden" name="pageNum" value="${cri.pageNum}">
                         <input type="hidden" name="amount" value="${cri.amount}">
                           <div class="fields">
                              <div class="field">
                                 <h4>번호</h4>
                                 <input name="bno" type="text" value="${board.bno}" readonly/>
                              </div>
                              <div class="field">
                                 <h4>*제목</h4>
                                 <input name="title" type="text" value="${board.title}"/>
                              </div>
                              <div class="field">
                                 <h4>*내용</h4>
                                 <textarea name="content" rows="6" style="resize:none">${board.content}</textarea>
                              </div>
                              <div class="field">
                                 <h4>작성자</h4>
                                 <input name="writer" type="text" value="${board.writer}" readonly/>
                              </div>
                           </div>
                           <ul class="actions special">
                              <li>
                                 <input type="submit" class="button" value="수정 완료"/>
                              </li>
                           </ul>
                        </form>
                     </div>
                              </div>

1-5-1. 수정 / 삭제 처리 후 이동

POST 방식으로 진행되는 수정과 삭제 처리는 BoardController에서 각각의 메소드 형태로 구현되었으므로 페이지 관련 파라미터 처리를 위해 변형해야 한다.

// 수정과 삭제는 성공 시 result에 success를 담아서 view에 전달하기.
    // 수정 처리와 테스트 구현
    @PostMapping("/modify")
    public String modify(BoardVO board, Criteria cri, RedirectAttributes rttr) {
        log.info("modify : " + board);

        if(service.modify(board)) {
            rttr.addFlashAttribute("result", "success");
        }

        /*
         * 항상 컨트롤러에 있는 클래스 타입의 매개변수는 생성자를 통해서 파라미터 값으로 초기화 한다.
         * 만약 전달받은 파라미터 값에 매핑되는 생성자가 없다면 값을 전달받을 수 없다.
            rttr.addAttribute("cri", cri); 이렇게 쓰면 안된다. 
         */

        // POST 방식으로 진행되는 수정과 삭제 처리는 보드컨트롤러에서 각각의 메서드 형태로 구현되었으므로 
//        페이지 관련 파라미터 처리를 위해 변형해야 한다.
        /*
         * Flash는 세션의 남용을 방지하고자 1개의 파라미터만 전달할 수 있다.
         * 따라서 여러 개를 전달할 수 있는 컬렉션에 담아서 넘기거나
         * URL에 붙여서 전달하는 addAttribute() 방식을 사용해야 한다.
         */

//        따라서 반드시 해당 객체의 생성자에 전달할 필드명과 일치하도록 설정해주어야 한다.
        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());
        return "redirect:/board/list";
    }

    // 삭제 처리와 테스트 구현
    @GetMapping("/remove")
    public String remove(@RequestParam("bno") Long bno, Criteria cri, RedirectAttributes rttr) {
        log.info("remove : " + bno);
        if(service.remove(bno)) {
            rttr.addFlashAttribute("result", "success");
        }
        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());
        return "redirect:/board/list";
    }

1-5-2. 수정 / 삭제 페이지에서 목록으로 이동

modify.jsp

<h3><a href="/board/list?pageNum=${cri.pageNum}&amount=${cri.amount}" class="button small">목록 보기</a></h3>

1-6. MyBatis에서 전체 데이터 개수 처리

최종적으로 데이터베이스에 있는 실제 모든 게시물의 수를 구해 PageDTO를 구성할 때 전달해 줘야 한다.

BoardMapper 인터페이스에 getTotalCount() 메소드 정의.

package com.koreait.mapper;

import java.util.List;

import com.koreait.domain.BoardVO;
import com.koreait.domain.Criteria;

public interface BoardMapper {
   //@Select("SELECT * FROM TBL_BOARD WHERE BNO > 0")
   public List<BoardVO> getList();

   public List<BoardVO> getListWithPaging(Criteria cri);

   public int getTotal();

   public void insert(BoardVO board);

   public void insertSelectKey_bno(BoardVO board);

   //read() 선언 후 테스트 : 게시글 상세보기
   public BoardVO read(Long bno);

   //delete() 선언 후 테스트 : 게시글 삭제
   //게시글 삭제 시 1이상의 값 리턴, 없으면 0 리턴
   public int delete(Long bno);

   public int update(BoardVO board);

   public int getTotalCount(Criteria cri);

}

BoardMapper.xml

<select id="getTotalCount" resultType="int">
        SELECT COUNT(*) FROM TBL_BOARD WHERE bno > 0
</select>

BoardService 메소드 추가

  //가져오기
   public int getTotal(Criteria cri);

BoardServiceImple

    @Override
    public int getTotal(Criteria cri) {
        return mapper.getTotal();
    }

BoardController 수정

    @GetMapping("/list")
    public void list(Criteria cri, Model model) {
        log.info("list");
        model.addAttribute("list", service.getList(cri));
        model.addAttribute("pageMaker", new PageDTO(cri, service.getTotal(cri)));
    }
반응형

댓글