본문 바로가기

TIL

<스프링 Spring> 페이징 + 검색 기능 구현

- 페이징 기능에 사용되는 컴포넌트

: 백엔드 - Controller, Service, Repository, Mapper.xml, VO, Pager / 프론트엔드 - JSP, JS, CSS

 

 

1) 페이징 기능 구현을 위해, VO 객체 외에도 페이징을 구현할 변수가 따로 필요하다. 이는 클래스 Pager를 생성하여 만든다. Pager 클래스에 담길 변수(필드)는 다음과 같다.

  • 전체 글의 갯수 totalCount
  • 전체 페이지의 수 totalPage
  • 페이지당 글의 수 perPage
  • 전체 블럭의 수 totalBlock
  • 페이지당 블럭의 수 perBlock
  • 현재 블럭 번호 curBlock
  • 게시판 테이블의 컬럼 kind
  • 검색어 search
  • 페이지 시작번호 startNum
  • 페이지 끝번호 lastNum
  • 마지막 블럭 조사 lastCheck
  • 첫 시작 행 startRow

이 변수들을 토대로 getter, setter도 생성해준다. lombok을 설치했다면, 애노테이션으로 해결한다.

 

 

2) Controller에서 글 목록 조회 시 페이징 처리도 함께 구현되도록 설정한다.

	@GetMapping("list")
	public ModelAndView getList(Pager pager, ModelAndView mv, NoticeVO noticeVO) throws Exception{
		List<NoticeVO> ar = noticeService.getList(pager);
		mv.addObject("noticeList", ar);
		mv.addObject("pager", pager);
		mv.setViewName("board/list");
		return mv;
	}

웹 브라우저에서 url로 list를 호출하고, 여기에 paging 처리가 더해져 url 뒤에 페이징 관련 파라미터 값이 붙게 된다. 그리고 이 값이 Controller의 getList 메서드를 통해 전달되는데, 매개변수로 url+파라미터 값을 받는다.

 

다만, 처음 list를 호출했을 경우에는 페이지 번호를 선택하여 호출한 것이 아닌, 전체 목록을 표시하는 첫 페이지인 것이므로 페이징 처리 관련한 파라미터 값에 기본으로 포함되는 kind(컬럼), search(검색), pageNum(페이지번호)는 모두 디폴트값으로 처리된다. 디폴트값 설정 코드는 Pager 클래스에 구현한다.

public String getKind() {
		return kind;
}

public void setKind(String kind) {
		this.kind = kind;
}

public String getSearch() {
		if(this.search==null) {
			this.search="";
		}
		return this.search;
}

public void setSearch(String search) {
		this.search = search;
}

public Integer getPageNum() {
		if(this.pageNum==null || this.pageNum<1) {
			this.pageNum=1;
		}
		return pageNum;
}

public void setPageNum(Integer pageNum) {
		this.pageNum = pageNum;
}

 

Controller에서는 먼저 Service의 getList 메서드를 호출하며 웹 브라우저로부터 받아온 페이징 관련 파라미터 값을 pager에 담아 함께 전달한다. 이후 Service로부터 리턴된 값은 List 타입의 ar 객체에 담아 ModelAndView의 객체로 이름을 붙여 추가해주고, 웹 브라우저에서 받았던 pager 값도 똑같이 추가해준 후, view 네임을 설정하여 리턴해준다.

 

 

3) Service에서는 호출받은 메서드를 마찬가지로 실행한다. 

public List<NoticeVO> getList(Pager pager) throws Exception{
	pager.makeRow();
		
	Long totalCount = noticeRepository.getTotalCount(pager);
	pager.makeNum(totalCount);
		
	return noticeRepository.getList(pager);
};

makeRow()와 makeNum() 메서드는 Pager 클래스에 구현하는데, 코드는 아래와 같다.

public void makeRow() {
		this.startRow = (this.getPageNum()-1)*this.getPerPage();
	}
	
public void makeNum(Long totalCount) {
		//전체페이지 갯수 구하기
		Long totalPage = totalCount/this.getPerPage();
		if(totalCount%this.getPerPage() !=0) {
			totalPage++;
		}
		
		//총 페이징 갯수 구하기
		Long perBlock=5L;
		Long totalBlock = totalPage/perBlock;
		if(totalPage%perBlock != 0) {
			totalBlock++;
		}
		
		//pageNum으로 현재 블럭 번호 구하기
		Long curBlock = this.getPageNum()/perBlock;
		if(this.getPageNum()%perBlock != 0) {
			curBlock++;
		}
		
		//curBlock으로 시작번호, 끝번호 구하기
		startNum = (curBlock-1)*perBlock+1;
		lastNum = curBlock*perBlock;
		
		if(curBlock==totalBlock) {
			lastCheck=true;
			lastNum=totalPage;
		}
}

 

 

4) Repository에서도 Service에서 호출받은 메서드를 실행한다. 실제 실행은 Mapper.xml에서 이루어진다.

public List<NoticeVO> getList(Pager pager) throws Exception;

 

 

5) Mapper.xml에는 다음과 같이 작성한다.

<select id="getList" parameterType="Pager" resultType="NoticeVO">
  		select * from destudynotice
  		where
  			<choose>
  				<when test="kind == 'writer'">writer</when>
  				<when test="kind == 'contents'">contents</when>
  				<otherwise>title</otherwise>
  			</choose>
  		like concat('%', #{search}, '%')
  		order by num desc
  		limit #{startRow}, #{perPage}
</select>
  	
<select id="getTotalCount" parameterType="Pager" resultType="Long">
  		select count(num) from notice
  		where
  			<choose>
  				<when test="kind == 'writer'">writer</when>
  				<when test="kind == 'contents'">contents</when>
  				<otherwise>title</otherwise>
  			</choose>
  		like concat('%', #{search}, '%')
</select>

list 호출 시, 파라미터 값으로 전달되는 값이 첫 페이지여서 kind와 search가 없는 경우는 디폴트값으로 title를 설정한다. 따라서 search 값은 빈 문자열로 전달된다. kind와 search 값이 존재하는 경우에는 like를 이용하여 search에 입력된 값이 포함되는 데이터를 가져올 수 있도록 '%' 문자열을 넣어 sql 구문을 작성한다.

 

그리고 다시 4) -> 3) -> 2) -> 1)의 순서로 처리된다.

 

 

6) JSP에는 검색 기능과 페이징 기능을 모두 작성해주는데, 페이징만 하려면 a 링크 태그를 이용하여 작성하면 된다. 두 가지를 같이 작성하는 이유는 페이징과 검색 기능이 공통되는 코드를 공유하기 때문에 하나의 코드로 두 개의 기능을 만들 수 있기 때문이다.

 

페이징만 구현하는 코드는 아래와 같다.

<section id="paging">
	<!-- 이전 버튼 -->
	<a href="./list?kind=${pager.kind}$search=${pager.search}&pageNum=${pager.startNum-1}">
		<button class="p" type="button">이전</button>
	</a>
	
    <!-- 현재 페이지 -->
	<c:forEach begin="${pager.startNum}" end="${pager.lastNum}" var="i">
		<a href="./list?kind=${pager.kind}&search=${pager.search}&pageNum=${i}">${i}</a>
	</c:forEach>
	
    <!-- 다음 버튼 -->
	<c:if test="${!pager.lastCheck}">
		<a href="./list?kind=${pager.kind}$search=${pager.search}&pageNum=${pager.lastNum+1}">
			<button class="p" type="button">다음</button>
		</a>
	</c:if>
</section>

 

페이징 + 검색 기능을 함께 구현하려면 아래와 같이 작성한다. 이 경우는 하나의 코드로 두 기능을 실행되도록 만들기 위해 JS 코드도 만들어준다.

<!-- 검색 -->
<form action="./list" id="frm">
	<input type="hidden" name="pageNum" value="1" id="pageNum">
	<select name="kind" id="kind">
		<option class="s" value="title">제목</option>
		<option class="s" value="contents">내용</option>
		<option class="s" value="writer">작성자</option>
	</select>
	<input type="text" name="search" id="search" value="${pager.search}">
	<button type="submit" id="btn">검색</button>
</form>

<!-- 페이징 -->
<section id="paging">
	<button class="p" data-list-pn="${pager.startNum-1}" type="button">이전</button>

	<c:forEach begin="${pager.startNum}" end="${pager.lastNum}" var="i">
		<span class="p" data-list-pn="${i}" >${i}</span>
	</c:forEach>

	<c:if test="${!pager.lastCheck}">
		<button class="p" data-list-pn="${pager.lastNum+1}" type="button">다음</button>
	</c:if>
</section>


<!-- 추가로 setKind 함수 호출 시 매개변수 넣어주기 -->
<script type="text/javascript">
	setKind("${pager.kind}");
</script>

 

JS 코드는 아래와 같다. 페이징에서 버튼을 클릭하면, 검색 기능의 코드에 있는 pageNum에 값을 넣어 입력 폼을 서버로 보내준다. 즉, 페이징 호출 -> 검색 기능의 입력 폼에 넣어 서버로 보내는 식으로 처리되는 것이다.

function setKind(kind) {
	$(".s").each(function() {
		if($(this).val()==kind){
			$(this).prop("selected", true);
		}
	})
};

$(".p").click(function() {
	const n= $(this).attr("data-list-pn");
	$("#pageNum").val(n);
	$('#frm').submit();
			
});