@Data
@Getter / @Setter, @ToString, @EqualsAndHashCode,
@RequiredArgsConstructor, @Value 를 포함한 어노테이션
mybatis.configuration.map-underscore-to-camel-case=true
applocation.properties 에서 설정함
mapper.xml 에서 resultmap으로 dto 와 column 매치하던 것을 더 간단하게 사용할 수 있음
@Data
@NoArgsConstructor
public class Notice {
// 주의 : foreach 내에서는 sequence 사용 불가능!
private long no;
private String userId;
private String userName;
private String title;
private String content;
private int hit;
@Builder
public Notice(int no, String userId, String userName, String title, String content, int hit) {
super();
this.no = no;
this.userId = userId;
this.userName = userName;
this.title = title;
this.content = content;
this.hit = hit;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SearchKeyword {
private String keyword;
}
@Data
public class PageRequestDTO {
private int pageNum; // 현재 페이지 번호
private int amount; // 페이지당 출력할 데이터 개수
public PageRequestDTO() {
this(1, 10);
}
@Builder
public PageRequestDTO(int pageNum, int amount) {
super();
this.pageNum = pageNum;
this.amount = amount;
}
}
@Data
public class PageResponseDTO {
// 화면 하단 출력할 페이지 개수
private int pageCount;
// (화면)시작 페이지 번호, 종료 페이지 번호
private int startPage, endPage;
// 이전 페이지, 다음 페이지 이동
private boolean prev, next;
// 전체 데이터 수
private int total;
// 마지막 페이지
private int realEnd;
// 현재 페이지 번호, 페이지당 출력할 데이터 개수를 위해 사용
private PageRequestDTO pageRequest;
public PageResponseDTO(){}
public PageResponseDTO(int total, int pageCount, PageRequestDTO pageRequest) {
this.total = total;
this.pageCount = pageCount;
this.pageRequest = pageRequest;
this.endPage = (int)(Math.ceil(pageRequest.getPageNum() * 1.0 / pageCount)) * pageCount;
this.startPage = endPage - (pageCount - 1);
realEnd = (int)(Math.ceil(total * 1.0 / pageRequest.getAmount()));
if(endPage > realEnd){
this.endPage = realEnd;
}
this.prev = this.startPage > 1;
this.next = this.endPage < realEnd;
}
}
@Controller
@RequiredArgsConstructor
public class NoticeController {
final NoticeMapper noticeMapper;
// insert
@GetMapping("/insert-notice")
public void insertNotice() {
// 1 ~ 200 범위 IntStream으로 입력함
IntStream.rangeClosed(1, 200)
.forEach(i -> {
Notice notice = Notice.builder()
.no(i)
.userId("userId : " + i)
.userName("userName : " + i)
.title("title : " + i)
.content("content : " + i)
.hit(i)
.build();
noticeMapper.insertNotice(notice);
});
}
// searchPage
@GetMapping("/search")
public String moveSearch() {
return "search";
}
// searchKeyword
@GetMapping("/search/keyword")
public String searchKeyword(@ModelAttribute SearchKeyword searchKeyword,
Model model) {
List<Notice> noticeList = noticeMapper.getNoticeBySearchKeyword(searchKeyword);
System.out.println(noticeList);
model.addAttribute("noticeList", noticeList);
return "search";
}
// Pagination
@GetMapping("/paging")
public String paging(@ModelAttribute PageRequestDTO pageRequest,
Model model) {
List<Notice> noticeList = noticeMapper.getNoticeByPagination(pageRequest);
int total = noticeMapper.getTotalCount();
PageResponseDTO pageResponseDTO = new PageResponseDTO(total, 10, pageRequest);
System.out.println(pageResponseDTO);
model.addAttribute("noticeList", noticeList);
model.addAttribute("pageInfo", pageResponseDTO);
return "pagination";
}
}
@Mapper
public interface NoticeMapper {
// insertNotice
public Integer insertNotice(Notice notice);
// searchKeyword
public List<Notice> getNoticeBySearchKeyword(SearchKeyword searchKeyword);
// getNoticeByPagination
public List<Notice> getNoticeByPagination(PageRequestDTO pageRequest);
// getTotalCount
public int getTotalCount();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.pagination.mapper.NoticeMapper">
<!-- insertNotice -->
<insert id="insertNotice" parameterType="com.spring.pagination.dto.Notice">
INSERT INTO notice (no, user_id, user_name, title, content, hit)
VALUES (#{no}, #{userId}, #{userName}, #{title}, #{content}, #{hit})
</insert>
<!-- getNoticeBySearchKeyword -->
<select id="getNoticeBySearchKeyword"
parameterType="com.spring.pagination.dto.SearchKeyword"
resultType="com.spring.pagination.dto.Notice">
SELECT no, user_id, user_name, title, content, hit
FROM notice
<trim prefix="WHERE" prefixOverrides="AND |OR">
<if test="keyword != null">
AND content LIKE '%'||#{keyword}||'%'
</if>
</trim>
</select>
<!-- getNoticeByPagination -->
<select id="getNoticeByPagination"
resultType="com.spring.pagination.dto.Notice"
parameterType="com.spring.pagination.dto.PageRequestDTO">
<![CDATA[
SELECT no, title, content, hit
FROM ( SELECT no, title, content, hit, rownum rn
FROM notice
WHERE rownum <= #{pageNum} * #{amount})
WHERE rn > (#{pageNum} - 1) * #{amount}
]]>
</select>
<!-- getTotalCount -->
<select id="getTotalCount" resultType="_int">
SELECT COUNT(no)
FROM notice
</select>
</mapper>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pagination</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<!-- https://getbootstrap.com/docs/5.3/forms/form-control/ -->
<div>
<form class="row justify-content-center g-3" action="/search/keyword" method="GET">
<div class="col-auto">
<label for="keyword" class="visually-hidden">Search</label>
<input
type="text"
class="form-control"
id="keyword"
placeholder="Keyword"
name="keyword"
value=${pageMaker.pageRequest.keyword}>
</div>
<div class="col-auto">
<input type="submit" class="btn btn-primary mb-3" value="Search" />
</div>
</form>
</div>
<!-- https://getbootstrap.com/docs/5.3/components/list-group/ -->
<div class="row justify-content-center">
<div class="col-6">
<ul class="list-group">
<c:if test="${not empty noticeList}">
<c:forEach var="notice" items="${noticeList}">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">
${notice.title}
</div>
${notice.content}
</div>
<span class="badge bg-primary rounded-pill">
${notice.hit}
</span>
</li>
</c:forEach>
</c:if>
</ul>
</div>
</div>
</div>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pagination</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<!-- https://getbootstrap.com/docs/5.3/components/list-group/ -->
<div class="row justify-content-center">
<div class="col-6">
<ul class="list-group">
<c:if test="${not empty noticeList}">
<c:forEach var="notice" items="${noticeList}">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">${notice.title}</div>
${notice.content}
</div> <span class="badge bg-primary rounded-pill">${notice.hit}</span>
</li>
</c:forEach>
</c:if>
</ul>
</div>
</div>
<!-- Paging -->
<!-- https://getbootstrap.com/docs/5.3/layout/columns/#alignment -->
<!-- https://getbootstrap.com/docs/5.3/components/pagination/#disabled-and-active-states -->
<div class="row justify-content-center">
<div class="col-6">
<nav class="page navigation">
<ul class="pagination">
<c:if test="${pageInfo.prev}">
<li class="page-item">
<a class="page-link" aria-label="Previous" href="/paging?pageNum=${pageInfo.startPage - 1}&amount=${pageInfo.pageRequest.amount}">Prev</a>
</li>
</c:if>
<c:forEach var="num" begin="${pageInfo.startPage}" end="${pageInfo.endPage}">
<li class="page-item ${pageInfo.pageRequest.pageNum == num ? "active" : "" } ">
<a class="page-link"
href="/paging?pageNum=${num}&amount=${pageInfo.pageRequest.amount}">${num}</a>
</li>
</c:forEach>
<c:if test="${pageInfo.next}">
<li class="page-item next">
<a class="page-link" aria-label="next" href="/paging?pageNum=${pageInfo.endPage + 1}&amount=${pageInfo.pageRequest.amount}">Next</a>
</li>
</c:if>
</ul>
</nav>
</div>
</div>
</div>
</body>
</html>
Mapper.xml 에서 파라미터 값을 찾지 못하는 예외가 종종 발생함
이에 따라 결과 또한 null로 넘겨받게 되는데 '%'||#{value}||'%' 이러한 문법처럼
Query에서 변수를 사용할 때 더 많이 고려해야될 것 같음
Pagination의 DTO 설계는 정말 알고리즘 사고가 많이 사용됬다고 느꼈음
아직 trim, like, where, if를 적재적소에 사용하는 감이 많이 부족하다고 느꼈음