Spring - 6 (확인창 / 페이징처리)
Delete 나 Modify의 결과는 success라는 문자열이 나오게 되는데
기존 list.jsp 코드의 스크립트 부분을 보면 전달된 값이 숫자였을 때 게시물 번호와 창이 뜨게 하였다.
이제 스크립트에 else문을 추가하여 success라는 값이 뜨게 만들어준다.
<script>
$(document).ready(function() {
console.log("목록 페이지0414");
$('#dataTable').DataTable({
"order" : [ [ 0, "desc" ] ], // 정렬 0컬럼의 내림차순으로
"paging" : false, // 페이징 표시 안함.
"bFilter" : false, // 검색창 표시 안함.
"info" : false
//안내창 표시 안함
});
$("#regBtn").on("click", function() {
self.location = "/board/register";
/* 아이디 regBtn 을 클릭한다면
현재창의 url를 쓰기로 변경 */
});
var result = '<c:out value="${result}"/>';
// 자바스크립트 형추론 이용.
checkModal(result);
// 게시판 번호를 매개변수로 전달하면서 checkModal 평션 호출
function checkModal(result) {
if (result === '') {
// ==는 값만 비교, === 은 값과 형식도 비교
return;
}
if ($.isNumeric(result)) { // 전달된 값이 숫자인가
if (parseInt(result) > 0) {
$(".modal-body").html("게시글 " + parseInt(result) + "번 등록");
}
} else {
$(".modal-body").html(result);
// 수정과 삭제시에는 success 라는 문자열이 전달 되므로, 숫자화 할 수 없음.
// 표시할 내용 만들기
}
$("#myModal").modal("show"); //모달창 표시
}
});
</script>
이제 페이징 처리를 구현해본다.
src/main/java의 domain 패키지에서 Criteria 클래스를 만들어준다.
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Criteria {
// Criteria : 페이징 처리 기준을 갖는 밸류 오브젝트
private int pageNum; // 현재 페이지 번호.
private int amount; // 페이지당 게시물수
public Criteria() {
this(1,10); // 아래쪽 전달값 2개 생성자 호출.
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
}
그다음 mapper패키지의 BoardMapper 인터페이스에서 getListWithPaging 메서드를 추가해준다.
package kr.icia.mapper;
import java.util.List;
import kr.icia.domain.BoardVO;
import kr.icia.domain.Criteria;
public interface BoardMapper {
public List<BoardVO> getList();
public List<BoardVO> getListWithPaging(Criteria cri);
public int insert(BoardVO board);
// bno는 시퀸스 자동 생성으로 나머지 값만 입력.
// 새로운 게시물 1개 추가.
public void insertSelectKey(BoardVO board);
// 생성되는 시퀸스 값을 확인하고 나머지 값 입력.
// 새로운 게시물 1개 추가의 다른 방식.
public BoardVO read(long l);
public int delete(long l);
public int update(BoardVO board);
}
다음은 src/main/resources의 mapper폴더의 BoardMapper.xml 파일에서 쿼리를 추가해준다.
<select id="getListWithPaging" resultType="com.review.domain.BoardVO">
<![CDATA[
select bno, title, content, writer, regdate, updatedate
from(
select /*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate,
updatedate from tbl_board
where rownum <= #{pageNum} * #{amount})
where rn > (#{pageNum}-1) * #{amount}
]]>
</select>
from 안의 select 문에서 힌트 주석(/*+ */)을 작성하여 사용한다. 주석에 꼭 "+"를 붙여야 힌트절이 실행된다.
여기서 pk_board라는 인덱스를 참조하는데 우리는 sqldeveloper에서 따로 만든적이없다.
이는 인덱스는 기본키나 유일키와 같은 제약조건을 지정하면 따로 생성하지 않더라도 자동으로 생성해준다.
우리가 처음 table을 생성할때 alter table ~~ constraint pk 해서 bno를 primary key로 지정하였을때 생성된듯하다.
기본 키나 유일 키는 데이터 무결성을 확인하기 위해하기 위해서 수시로 데이터를 검색하기 때문에
빠른 조회를 목적으로 오라클에서 내부적으로 해당 컬럼에 인덱스를 자동으로 생성하는 것이다.
인데스 힌트란?
- 적절한 인덱스 힌트를 사용하면 쿼리의 수행 속도를 향상
- ORDER BY를 사용하지 않아도 인덱스의 컬럼 순서로 정렬되어 조회
- INDEX (INDEX_ASC) : 오름차순 정렬, INDEX_DESC : 내림차순 정렬
- 멀티라인 주석 (/*+ */) 과 싱글라인 주석 (--+) 모두 인덱스 힌트를 사용할 수 있다.
- 여러개의 복한 인덱스 힌트를 사용할 수 있다. (/*+ INDEX(.....) INDEX(....) */)
코드를 해석하자면 새로운 인덱스 테이블을 만들어서 받아온 pageNum과 amount의 곱한 수가 rownum보다 크거나 같은 조건을 가질때의 데이터들이 인덱스 테이블에 출력된다. 만약 pageNum이 1이고 amount가 10이라면 rownum은 10보다 작거나 같은 숫자만 가능하다는 이야기이다.
SQLDEVELOPER에서 한 번 실행해보면 이해가 쉽다.
select /*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate,
updatedate from tbl_board
where rownum <= 1 * 10
정말 10개의 게시물만 출력되는것을 볼 수 있다. 만약 pageNum이나 amount의 숫자를 바꾼다면 그수만큼 나올것이다.
쿼리가 잘 동작하는지 Test클래스로 가서 실행해본다.
@Test
public void testPaging() {
Criteria cri = new Criteria();
cri.setPageNum(1); // 페이지 번호
cri.setAmount(10); // 페이지당 보여줄 게시물 갯수.
List<BoardVO> list = mapper.getListWithPaging(cri);
// 10개 게시물 리턴
list.forEach(board -> log.info(board.getBno()));
// 스트림 처리로 게시물 번호만 로그로 출력.
}
Criteria의 생성자로 pageNum과 amount의 1과 10이 들어가고
마이바티스 쿼리로 뽑아온 데이터를 list변수에 담아서 리턴하며,
그다음 forEach로 순환하며 람다식으로 게시물 번호만 log에 출력한다.
그전에 기존에 등록한 게시물이 많아야 확인할 수 있으므로, sqldeveloper에서 데이터를 넣어준다.
insert into tbl_board(bno, title, content, writer, regdate, updatedate)
select SEQ_BOARD.nextval,title,content,writer,regdate,sysdate from tbl_board;
기존 테이블의 2배 수로 복사되어 추가된다.
추가를 한 뒤, commit을 해줘야 반영이 된다. 안 해주면 웹페이지에서 뜨지를 않는다.
바로 테스트를 실행해보면 아래처럼 뜬다.
페이지에서 보여줄 개수를 10개로 설정한 결과 10개가 뜨는 것을 볼 수 있다.
이제 실제로 웹페이지에서 동작할 수 있도록 한다.
BoardService 인터페이스에 getList에 매개변수를 준 getlist를 정의한다.
public List<BoardVO> getList(Criteria cri);
BoardServiceImp클래스를 수정한다.
@Override
public List<BoardVO> getList(Criteria cri) {
log.info("getListWithPaging....." + cri);
// 페이징 조건대로 10개씩 추출.
return mapper.getListWithPaging(cri);
}
domain패키지에서 pageDTO라는 페이지 번호 처리를 위한 클래스를 만들어준다.
package kr.icia.domain;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class PageDTO {
// DTO : data transfer object
private int startPage; // 페이징 시작, 끝번호를 10개씩 표시한다면, 1 혹은 11 혹은 21...
private int endPage; // 1페이지라고 가정하면 endPage는 10
private boolean prev, next; // 이전, 다음 버튼. 경우에 따라서 보여주기.
private int total; // 총 게시물 수
private Criteria cri; // 현재페이지와 페이지당 게시물 수
public PageDTO(Criteria cri, int total) {
this.cri = cri;
this.total = total;
this.endPage
= (int) (Math.ceil(cri.getPageNum() / 10.0)) * 10;
// Math.ceil은 소수점 이하를 올림한다.
// 1페이지라고 가정하면 endPage는 10
this.startPage = this.endPage - 9; // 1
int realEnd
= (int) (Math.ceil((total * 1.0)/ cri.getAmount()));
// 총게시물이 20개라고 가정하면 realEnd = 2
// 페이지당 보여줄 게시물 수는 10개로 가정
if (realEnd < this.endPage) {
this.endPage = realEnd;
}
this.prev = this.startPage > 1;
this.next = this.endPage < realEnd;
}
}
BoardController를 수정한다.
@GetMapping("/list")
public void list(Model model, Criteria cri) {
log.info("list");
model.addAttribute("list",service.getList(cri));
// /WEB-INF/views/list.jsp
// 컨트롤러에서 리턴하는 문자열이 없다면 요청한 URL과 매칭되는 jsp를 우선 검색.
model.addAttribute("pageMaker", new PageDTO(cri, 190));
// 임의로 총게시물 190로 설정.
}
이제 list.jsp에 페이지 부분이 보일 수 있도록 수정한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!-- jstl core 쓸때 태그로 c로 표시 -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!-- jstl fmt 쓸때 위와 같음, fmt : fomatter 형식 맞춰서 표시 -->
<%@ include file="../includes/header.jsp"%>
<h1 class="h3 mb-2 text-gray-800">Tables</h1>
<p class="mb-4">
DataTables is a third party plugin that is used to generate the demo
table below. For more information about DataTables, please visit the <a
target="_blank" href="https://datatables.net">official DataTables
documentation</a>.
</p>
<!-- DataTales Example -->
<div class="card shadow mb-4">
<div class="card-header py-3" align="right">
<button id="regBtn" style="color: green;">글쓰기</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%"
cellspacing="0">
<thead>
<tr>
<th>#번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>수정일</th>
</tr>
</thead>
<tbody>
<c:forEach var="board" items="${list }">
<tr>
<td><c:out value="${board.bno }"></c:out></td>
<td><a href="/board/get?bno=${board.bno }"><c:out value="${board.title }"></c:out></a></td>
<td><c:out value="${board.writer }"></c:out></td>
<td><fmt:formatDate pattern="yyyy-MM-dd"
value="${board.regdate }" /></td>
<td><fmt:formatDate pattern="yyyy-MM-dd"
value="${board.updateDate }" /></td>
</tr>
</c:forEach>
</tbody>
</table>
<br>
<div>
<ul class="pagination justify-content-center">
<c:if test="${pageMaker.prev }">
<li class="page-item previous">
<a href="${pageMaker.startPage-1 }" class="page-link">Prev</a>
</li>
</c:if>
<c:forEach var="num" begin="${pageMaker.startPage }" end="${pageMaker.endPage }">
<li class='page-item ${pageMaker.cri.pageNum==num?"active":"" }'>
<a href="${num }" class="page-link">${num }</a>
</c:forEach>
<c:if test="${pageMaker.next }">
<li class="page-item next">
<a href="${pageMaker.endPage+1 }" class="page-link">Next</a>
</li>
</c:if>
</ul>
</div>
</div>
</div>
</div>
<!-- 알림창 -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
console.log("목록 페이지0414");
$('#dataTable').DataTable({
"order" : [ [ 0, "desc" ]], // 정렬 0컬럼의 내림차순으로
"paging" : false, // 페이징 표시 안함.
"bFilter" : false, // 검색창 표시 안함.
"info" : false
//안내창 표시 안함
});
$("#regBtn").on("click", function() {
self.location = "/board/register";
/* 아이디 regBtn 을 클릭한다면
현재창의 url를 쓰기로 변경 */
});
var result = '<c:out value="${result}"/>';
// 자바스크립트 형추론 이용.
checkModal(result);
// 게시판 번호를 매개변수로 전달하면서 checkModal 평션 호출
function checkModal(result) {
if(result === ''){
// ==는 값만 비교, === 은 값과 형식도 비교
return;
}
if($.isNumeric(result)){
if(parseInt(result) > 0) {
$(".modal-body").html("게시글 " + parseInt(result) + "번이 등록");
// 표시할 내용 만들기
}
} else{
$(".modal-body").html(result);
}
$("#myModal").modal("show"); //모달창 표시
}
});
</script>
<%@ include file="../includes/footer.jsp"%>
<c:if> 태그는 if문처럼 쓰이는 태그이며 test속성을 필수값으로 가진다.
Controll에서 요청하여 PageDTO에서 pageMaker.prev 값을 가져온다.
prev의 타입은 boolean 타입이며 this.prev = this.startPage > 1; 일때 true값, 아니면 false이므로
true값이 들어간다면 실행되는 구문이다.
prev와 next의 <a>태그 링크에서 startPage와 endPage에 각각 -1 과 +1을 하는데 이는
-1만큼 페이지이동, +1 페이지 이동을 나타낸다.
<c:forEach> 태그는 반복문을 나타낸다. 시작값이 startPage, 끝값이 endPage로 시작값부터 끝값까지
1씩 증가하면서 반복한다. 반복하면서 num에 증감되는 숫자가 들어가는데 그 숫자가 페이지 숫자이다.
${pageMaker.cri.pageNum==num?"active":"" 부분은 삼항연산자로 pageNum이랑 num이랑 같으면 acrtive클래스가 추가되며, 아니면 공백으로 실행되지 않는다.
<c:if test="${pageMaker.next }"> 부분은 위의 prev와 마찬가지로 동작한다.
하지만 아직 클릭하면 페이지로 넘어가는 이벤트를 구현하지 않았다.