[Spring Boot] Search, Pagination

JH·2023년 6월 9일

Spring Boot

목록 보기
2/4

1. TIL

A. @Data, applocation.properties

@Data
@Getter / @Setter, @ToString, @EqualsAndHashCode,
@RequiredArgsConstructor, @Value 를 포함한 어노테이션

mybatis.configuration.map-underscore-to-camel-case=true
applocation.properties 에서 설정함
mapper.xml 에서 resultmap으로 dto 와 column 매치하던 것을 더 간단하게 사용할 수 있음


B. Search, Pagination

1. DTO

Notice (출력 객체)

@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;
	}
}

SearchKeyword (검색 전용 객체)

@Data
@AllArgsConstructor
@NoArgsConstructor

public class SearchKeyword {
	private String keyword;
}

PageRequestDTO (페이지 View 요청 객체)

@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;
	}
}

PageResponseDTO (Pagination을 위한 객체)

@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;
    }
}

2. Controller

@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";
	}
}

3. Mapper

Mapper Interface (Proxy)

@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();
}

Mapper.xml

<?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>

4. Jsp

<%@ 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>

pagination

<%@ 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>


2. 느낀점

Mapper.xml 에서 파라미터 값을 찾지 못하는 예외가 종종 발생함
이에 따라 결과 또한 null로 넘겨받게 되는데 '%'||#{value}||'%' 이러한 문법처럼
Query에서 변수를 사용할 때 더 많이 고려해야될 것 같음

Pagination의 DTO 설계는 정말 알고리즘 사고가 많이 사용됬다고 느꼈음

아직 trim, like, where, if를 적재적소에 사용하는 감이 많이 부족하다고 느꼈음

profile
잘해볼게요

0개의 댓글