스프링으로 쇼핑몰을 만들어보자20 - 작가목록 기능 구현 3
기능구현
- 회원가입(주소API연동, 이메일 인증, ajax를 사용하여 아이디 중복검사)
- 관리자 페이지(인터셉터 적용, 상품관리, 회원관리)
- 업로드(이미지 등록, 수정, 삭제)
- 검색
- 페이징
- 구매(장바구니, 포인트사용)
- 댓글(등록,수정,삭제)
- 중간에 막혔거나 에러 해결 못하겠으면 ybnr92@gmail.com 으로 문의
목표
- 페이지네이션 구현
- 사용자가 클릭을 통해 이동할 수 있도록 해주는 페이지네이션 구현을 목표로 한다.
순서
- PageDTO 클래스 작성
- 전체 데이터 개수
- Controller 작업
- View 처리
PageDTO 클래스 작성
com.store.model
패키지에 PageDTO 클래스를 생성 후 아래의 변수, 생성자를 작성한다. PageDTO 클래스의 데이터들은 페이지 이동 인터페이스를 출력시키는데 필요로 한 데이터들의 모임이다.
package com.store.model;
public class PageDTO {
/* 페이지 시작 번호 */
private int pageStart;
/* 페이지 끝 번호 */
private int pageEnd;
/* 이전, 다음 버튼 존재 유무 */
private boolean next, prev;
/* 행 전체 개수 */
private int total;
/* 현재 페이지 번호(pageNum), 행 표시 수(amount), 검색 키워드(keyword), 검색 종류(type) */
private Criteria cri;
/* 생성자(클래스 호출 시 각 변수 값 초기화) */
public PageDTO(Criteria cri, int total) {
/* cri, total 초기화 */
this.cri = cri;
this.total = total;
/* 페이지 끝 번호 */
this.pageEnd = (int)(Math.ceil(cri.getPageNum()/10.0))*10;
/* 페이지 시작 번호 */
this.pageStart = this.pageEnd - 9;
/* 전체 마지막 페이지 번호 */
int realEnd = (int)(Math.ceil(total*1.0/cri.getAmount()));
/* 페이지 끝 번호 유효성 체크 */
if (realEnd < pageEnd) {
this.pageEnd = realEnd;
}
/* 이전 버튼 값 초기화 */
this.prev = this.pageStart > 1;
/* 다음 버튼 값 초기화 */
this.next = this.pageEnd < realEnd;
}
}
- 선언된 변수들에 대한 getter()/setter(), toString() 메서드를 추가한다.
package com.store.model;
public class PageDTO {
/* 페이지 시작 번호 */
private int pageStart;
/* 페이지 끝 번호 */
private int pageEnd;
/* 이전, 다음 버튼 존재 유무 */
private boolean next, prev;
/* 행 전체 개수 */
private int total;
/* 현재 페이지 번호(pageNum), 행 표시 수(amount), 검색 키워드(keyword), 검색 종류(type) */
private Criteria cri;
/* 생성자(클래스 호출 시 각 변수 값 초기화) */
public PageDTO(Criteria cri, int total) {
/* cri, total 초기화 */
this.cri = cri;
this.total = total;
/* 페이지 끝 번호 */
this.pageEnd = (int)(Math.ceil(cri.getPageNum()/10.0))*10;
/* 페이지 시작 번호 */
this.pageStart = this.pageEnd - 9;
/* 전체 마지막 페이지 번호 */
int realEnd = (int)(Math.ceil(total*1.0/cri.getAmount()));
/* 페이지 끝 번호 유효성 체크 */
if (realEnd < pageEnd) {
this.pageEnd = realEnd;
}
/* 이전 버튼 값 초기화 */
this.prev = this.pageStart > 1;
/* 다음 버튼 값 초기화 */
this.next = this.pageEnd < realEnd;
}
public int getPageStart() {
return pageStart;
}
public void setPageStart(int pageStart) {
this.pageStart = pageStart;
}
public int getPageEnd() {
return pageEnd;
}
public void setPageEnd(int pageEnd) {
this.pageEnd = pageEnd;
}
public boolean isNext() {
return next;
}
public void setNext(boolean next) {
this.next = next;
}
public boolean isPrev() {
return prev;
}
public void setPrev(boolean prev) {
this.prev = prev;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public Criteria getCri() {
return cri;
}
public void setCri(Criteria cri) {
this.cri = cri;
}
@Override
public String toString() {
return "PageDTO [pageStart=" + pageStart + ", pageEnd=" + pageEnd + ", next=" + next + ", prev=" + prev
+ ", total=" + total + ", cri=" + cri + "]";
}
}
전체 데이터 개수(total)
- PageDTO 클래스를 정의하였는데 해당 클래스를 인스턴스화 시키기 위해서는 Criteria 클래스의 데이터와 전체 데이터 개수(total) 값이 필요하다. 왜냐하면 두 개의 데이터를 PageDTO 클래스 생성자 파라미터로 부여했기 때문이다.
- Criteria 클래스는 이미 정의되어 있기도 하고 PageDTO 클래스를 사용할 url 매핑 메서드인 authorManageGET()은 뷰로부터 Criteria 클래스 데이터를 받아오기 때문에 전체 데이터 개수(total)에 대한 값만 구해주면 된다.
AuthorMapper.java 인터페이스
- author 테이블의 총 행의 개수를 구하는 쿼리를 호출하는 메서드를 선언한다.
- 총 개수 값을 반환받아야 하기 때문에 메서드 선언부의 리턴 타입은 int이다.
- 그리고 조건문에 사용할
keyword
데이터를 전달받기 위해 파라미터로 Criteria 클래스를 부여하였다.
package com.store.mapper;
import java.util.List;
import com.store.model.AuthorVO;
import com.store.model.Criteria;
public interface AuthorMapper {
/* 작가 등록 */
public void authorEnroll(AuthorVO author);
/* 작가 목록 */
public List<AuthorVO> authorGetList(Criteria cri);
/* 작가 총 수 */
public int authorGetTotal(Criteria cri);
}
AuthorMapper.xml 인터페이스
- Mapper 인터페이스에서 선언한 메서드가 실행할 쿼리를 작성한다.
- 총 개수를 구하는 것이기 때문에 count(*)를 사용하였다. authorGetList() 쿼리에서 적용되는 조건문을 같이 작성해주어야 한다. 왜냐하면 검색조건에 따라 페이지 개수가 변해야 하는데 조건문을 적용해주지 않는다면 뷰에 출력되는 페이지 이동 인터페이스에 실제 존재해야 할 페이지 개수보다 더 많은 페이지 번호가 출력되기 때문이다.
<?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.store.mapper.AuthorMapper">
<!-- 작가 등록 -->
<insert id="authorEnroll">
insert into author(authorName, nationId, authorIntro) values(#{authorName}, #{nationId}, #{authorIntro});
</insert>
<!-- 작가 목록 -->
<select id="authorGetList" resultType="com.store.model.AuthorVO">
select authorid, authorname, nationid, regdate, updatedate
from author
<if test="keyword != null">
where authorname like concat('%',#{keyword}, '%')
</if>
order by authorid desc
limit #{skip}, #{amount}
</select>
<!-- 작가 수 -->
<select id="authorGetTotal" resultType="int">
select count(*) from author
<if test="keyword != null">
where authorname like concat('%', #{keyword}, '%');
</if>
</select>
</mapper>
AuthorMapper 메서드 테스트
- AuthorMapperTests.java에서 작성한 Mapper 메서드가 정상적으로 동작하는지 junit 테스트를 한다.
/* 작가 총 수 */
@Test
public void authorGetTotalTest() throws Exception{
Criteria cri = new Criteria();
cri.setKeyword("이작가야");
int total = mapper.authorGetTotal(cri);
System.out.println("total ......... : " + total);
}
Service 작성
- AuthorService.java에 앞서 작성한 Mapper 메서드를 호출한 메서드를 선언한다.
package com.store.service;
import java.util.List;
import com.store.model.AuthorVO;
import com.store.model.Criteria;
public interface AuthorService {
/* 작가 등록 */
public void authorEnroll(AuthorVO author) throws Exception;
/* 작가 목록 */
public List<AuthorVO> authorGetList(Criteria cri) throws Exception;
/* 작가 총 수 */
public int authorGetTotal(Criteria cri) throws Exception;
}
- AuthorServiceImpl.java에 오버라이딩하여 인터페이스에서 메서드의 구현부를 작성한다.
package com.store.service;
import java.util.List;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.store.mapper.AuthorMapper;
import com.store.model.AuthorVO;
import com.store.model.Criteria;
@Service
public class AuthorServiceImpl implements AuthorService{
private static final Logger log = LoggerFactory.getLogger(AuthorServiceImpl.class);
@Autowired
AuthorMapper authorMapper;
/* 작가 등록 */
@Override
public void authorEnroll(AuthorVO author) throws Exception {
authorMapper.authorEnroll(author);
}
/* 작가 목록 */
@Override
public List<AuthorVO> authorGetList(Criteria cri) throws Exception {
log.info("(service)authorGetList()......." + cri);
return authorMapper.authorGetList(cri);
}
/* 작가 총 수 */
@Override
public int authorGetTotal(Criteria cri) throws Exception{
log.info("(service)authorGetTotal ....... :" + cri);
return authorMapper.authorGetTotal(cri);
}
}
Controller 작업
- total의 값을 얻기 위해 AuthorService 클래스의 authorGetTotal()을 호출한 후 해당 값과 Criteria 클래스 데이터를 인자값으로 부여하여 PageDTO를 인스턴스화 시킨다.
/* 작가 관리 페이지 접속 */
@RequestMapping(value = "authorManage", method = RequestMethod.GET)
public void anthorManageGET(Criteria cri, Model model) throws Exception {
logger.info(">>>>>>>>>> 작가 관리 페이지 접속");
/* 작가 목록 출력 데이터 */
List list = authorService.authorGetList(cri);
model.addAttribute("list", list);
/* 페이지 이동 인터페이스 데이터 */
model.addAttribute("pageMaker", new PageDTO(cri, authorService.authorGetTotal(cri)));
}
View 처리
- 서버로부터 전달받은 pageMaker 데이터를 활용하여 페이지 이동 인터페이스를 구현한다.
- authorManage.jsp에 class 속성 값이 author_table_wrap인 div 태그 바로 다음 순서에 아래의 코드를 추가한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="../resources/css/admin/authorManage.css?after">
<script
src="https://code.jquery.com/jquery-3.4.1.js"
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
crossorigin="anonymous"></script>
</head>
</head>
<body>
<%@include file="../includes/admin/header.jsp" %>
<div class="admin_content_wrap">
<div class="admin_content_subject"><span>작가 관리</span></div>
<div class="author_table_wrap">
<table class="author_table">
<thead>
<tr>
<td class="th_column_1">작가 번호</td>
<td class="th_column_2">작가 이름</td>
<td class="th_column_3">작가 국가</td>
<td class="th_column_4">등록 날짜</td>
<td class="th_column_5">수정 날짜</td>
</tr>
</thead>
<c:forEach items="${list}" var="list">
<tr>
<td><c:out value="${list.authorId}"></c:out> </td>
<td><c:out value="${list.authorName}"></c:out></td>
<td><c:out value="${list.nationName}"></c:out> </td>
<td><fmt:formatDate value="${list.regDate}" pattern="yyyy-MM-dd"/></td>
<td><fmt:formatDate value="${list.updateDate}" pattern="yyyy-MM-dd"/></td>
</tr>
</c:forEach>
</table>
</div>
<!-- 페이지 이동 인터페이스 영역 -->
<div class="pageMaker_wrap" >
<ul class="pageMaker">
<!-- 이전 버튼 -->
<c:if test="${pageMaker.prev}">
<li class="pageMaker_btn prev">
<a href="${pageMaker.pageStart - 1}">이전</a>
</li>
</c:if>
<!-- 페이지 번호 -->
<c:forEach begin="${pageMaker.pageStart}" end="${pageMaker.pageEnd}" var="num">
<li class="pageMaker_btn ${pageMaker.cri.pageNum == num ? "active":""}">
<a href="${num}">${num}</a>
</li>
</c:forEach>
<!-- 다음 버튼 -->
<c:if test="${pageMaker.next}">
<li class="pageMaker_btn next">
<a href="${pageMaker.pageEnd + 1 }">다음</a>
</li>
</c:if>
</ul>
</div>
<form id="moveForm" action="/admin/authorManage" method="get">
<input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum}">
<input type="hidden" name="amount" value="${pageMaker.cri.amount}">
<input type="hidden" name="keyword" value="${pageMaker.cri.keyword}">
</form>
</div>
<%@include file="../includes/admin/footer.jsp" %>
<script>
$(document).ready(function() {
//let result = "${enroll_result}";
let result = '<c:out value="${enroll_result}"/>';
checkResult(result);
function checkResult(result) {
if (result === '') {
return;
}
alert("작가'${enroll_result}' 님을 등록하였습니다.");
}
});
</script>
</body>
</html>
- 페이지 이동 버튼이 동작하도록 script 태그 내에 아래의 js 코드를 추가한다.
let moveForm = $('#moveform');
/* 페이지 이동 버튼 */
$(".pageMaker_btn a").on("click", function(e) {
e.preventDefault();
moveForm.find("input[name='pageNum']").val($(this).attr("href"));
moveForm.submit();
});
- css코드를 authorManage.css에 추가한다.
/* 페이지 버튼 인터페이스 */
.pageMaker_wrap{
text-align: center;
margin-top: 30px;
margin-bottom: 40px;
}
.pageMaker_wrap a{
color : black;
}
.pageMaker{
list-style: none;
display: inline-block;
}
.pageMaker_btn {
float: left;
width: 40px;
height: 40px;
line-height: 40px;
margin-left: 20px;
}
.next, .prev {
border: 1px solid #ccc;
padding: 0 10px;
}
.next a, .prev a {
color: #ccc;
}
.active{ /* 현재 페이지 버튼 */
border : 2px solid black;
font-weight:400;
}
- 이래저래 바쁘다는 핑계로 업로드 기간이 늦었는데, 시리즈를 보며 공부하던 분들에겐 죄송하다는 말씀을 드리며, 시간나는대로 부지런히 올리겠습니다.
Leave a comment