Spring

230208 Spring 페이징, MySQL limit함수

주영재 2023. 2. 8. 20:10

페이징


페이징은 get방식으로 처리한다.
이동할 때 페이지 번호를 가지고 다닌다.
페이징 처리하는 로직을 클래스로 분류한다
->Criteria클래스, PageVO클래스
sql에 변경을 주는 클래스, sql로 화면을 그리는 클래스. (반드시는 아님)




MYSQL


오라클은 limit함수가 없어서 인라인뷰로 해줘야 한다. 
MySQL에서 페이징은 limit함수를 이용한다!

 


limit(데이터시작, 데이터개수)

select * from table
order by num desc
limit 0,10;

 1~10번 데이터가 조회된다.

select * from table
order by num desc
limit 10,20;

11번~30번 데이터가 조회된다.

select *
from trip order by tno desc limit 0,10; -- 1번에서 10개의 데이터

select * from trip order by tno desc limit 10,10; -- 11번에서 10개

select * from trip order by tno desc limit 10,50; -- 11번에서 50개

 


Critera클래스

-데이터시작과 데이터개수를 뽑아서 sql로 넘겨준다.
pageNum//페이지번호
count //몇개의 데이터를 보여줄건가.

기본생성자로 pageNum=1, count=10을 넣고
매개변수를 받는 생성자에서 사용자가 원하는 걸 넣어준다.

 


계산하는 메서드를 만들어서 sql문에 전달하게 해준다.

public int getPageStart() {
	return (pageNum - 1) * count;
}

 

처럼.

pageNum은 클릭하는 페이지, count는 값이 변할 수 있으니 그대로.

1페이지를 누르면 (1-1)*10=0, 카운트는 10.
그래서 limit에서 0,10이 되니 1~10번 글을 뽑고,

가령 3페이지를 누르면 (3-1)*10=20, 카운트는 10.
->limit 20,10이니 21번~31번 글을 뽑는다.

 

이 함수를 sql문으로.

select * from table order by num desc limit #{pageStart}, #{count}

Criteria 클래스 생성

-sql문에 페이지번호, 데이터개수 전달해줄 클래스

기본생성자를 만들 때 초기값을 지정해주려면 롬복을 사용할 수 없다.

getter setter toString은 롬복 @Data로.

getPageStart를 getter로 메서드를 만들어서 sql문에서 pageStart를 쓸 수 있다.

 

Criteria.java

package com.cordhistroy.myweb.util;

import lombok.Data;

//sql문에 페이지번호, 데이터개수 전달해줄 클래스
@Data
public class Criteria {
	
	private int page; //페이지번호
	private int amount; //데이터개수
	
	public Criteria() {
		this.page=1;
		this.amount=10;
	}
	
	public Criteria (int page, int amount) {
		this.page=page;
		this.amount=amount;
	}

	//limit함수의 페이지시작 부분에 들어갈 getter
	public int getPageStart() {
		return (page-1)*amount;
	}
	
}

Controller


spring은 vo를 여러개 매개변수로 받을 수 있다.

	//목록
	@RequestMapping("/notice_list")
	public String notice_list(Criteria cri, Model model) {
		
		//데이터
		ArrayList<TripVO> list = tripService.getList(cri);
		
		//페이지네이션
		int total=tripService.getTotal();
		PageVO pageVO=new PageVO(cri, total);
		System.out.println(pageVO.toString());
		
		
		//값 넘기기
		model.addAttribute("list",list);
		model.addAttribute(pageVO);
		
		return "trip/notice_list";
	}

 

 

 

mapper.xml

<select id="getList" resultType="TripVO" >
	select * from trip
	order by tno desc limit #{pageStart}, #{amount}
</select>

getPageStart로 지정된 pageStart부터 amount만큼의 컬럼을 화면에 그린다. 

 

가령 전체에서 9페이지고 10개씩 뿌려진다면, 

(9-1)*10인 80이 pageStart가 된다.

limit 80,10이므로 9페이지에선 81~90번 글이 나오는 것.

1페이지면 1번~10번글이 나온다.


PageVO 클래스

-페이징 계산 처리 클래스

 

  1. total : 게시판 글 전체 개수
  2. endPage : 게시판 화면에 보여질 마지막 페이지 번호
    -11~20페이지 클릭시 20
  3. startPage : 게시판 화면에 보여질 첫번째 페이지 번호
    -endPage가 20일때 11
  4. realEnd : 게시판의 실제 마지막 페이지 번호
    -총 게시물이 50개 일 때 5, 51개라면 6
  5. prev : 이전 페이지 버튼 활성화 여부
    -startPage가 1이면 비활성화, 1이 아니면 활성화.
  6. next : 다음 페이지 버튼 활성화 여부
    -realEnd가 endPage보다 크면 활성화.



이 클래스는 외우지 말고 이해할 것.

pageVO는 생성되는 순간 값을 받아야 한다. 생성자 안에서 처리할 것.


+)Math.ceil-올림

 

이전버튼과 다음버튼은, 1~10/ 11~20/ 21~30을 왔다갔다하는 버튼임

 


PageVO

package com.cordhistroy.myweb.util;

import lombok.Data;

//화면에 그려지는 페이지네이션의 값을 계산하는 클래스
@Data
public class PageVO {
	
	private int end; //페이지네이션 끝번호
	private int start; //페이지네이션 시작번호
	private boolean next; //다음버튼 활성화여부
	private boolean prev; //이전버튼 활성화여부
	private int realEnd; //페이지네이션 실제끝번호
	
	private int page; //사용자가 조회하는 페이지번호
	private int amount; //화면의 1페이지에 나타나는 데이터개수
	private int total; //전체게시글 수
	private Criteria cri; //페이지기준클래스
	
	private int pageCnt=5; //페이지네이션 개수
	
	//생성자 - pageVO가 만들어질 때 Criteria와 total을 반드시 받아야 한다.
	public PageVO(Criteria cri, int total) {
		//계산에 필요한 값(페이지번호, 데이터개수, 전체게시글수, cri)을 초기화
		this.page=cri.getPage();
		this.amount=cri.getAmount();
		this.total = total;
		this.cri=cri;
		
		//1. 끝페이지
		//page가 1~10 ->끝페이지 10
		//page가 11~20 ->끝페이지 20
		//(int)Math.ceil(페이지번호/10.0)*페이지네이션 수
		this.end=(int)Math.ceil(this.page/(double)pageCnt)*pageCnt;
		
		//2. 시작페이지번호 계산
		//end - 페이지네이션 수 + 1
		this.start=this.end-pageCnt+1;
		
		//3. 실제끝번호 계산
		//데이터가 60개라고 가정할 때, end = 6
		//데이터가 112개라고 가정할 때, 11번페이지 조회시 end=12
		//데이터가 356개라고 가정할 때, 32번페이지 조회시 end=36
		//(int)Math.ceil(전체게시글 수 / 데이터개수)
		this.realEnd=(int)Math.ceil(total/(double)this.amount);
		
		//4. 마지막 페이지번호의 재계산이 필요.
		//데이터가 112개라고 가정할 때, 5번페이지 조회시 end=10, realEnd=12 --->10
		//데이터가 112개라고 가정할 때, 11번페이지 조회시, end=20, realEnd=12 --->12
		//end>realEnd면 realEnd가 마지막페이지여야 한다.
		//끝번호>실제끝번호 이면 실제끝번호를 따라감
		this.end = this.end>this.realEnd?this.realEnd:this.end;
		
		//5. 이전버튼
		//start는 1, 11, 21, 31 ...로 증가되는데 1보다 크면 true
		this.prev=this.start>1;
		
		//6.다음버튼
		//조건- realEnd가 end보다 크면 true
		this.next=this.realEnd>this.end;
		
	}
	
	
}

-getter, setter는 @Data로.
-페이지네이션:한번에 보여줄 페이지 수

 


Controller에서 전체게시글 수 조회가 필요.

int total=tripService.getTotal();
PageVO pageVO=new PageVO(cri, total);

public int getTotal();//전체게시글수 조회

 

 

mapper.xml

<select id="getTotal" resultType="int">
  select count(*) as total from trip
</select>

 


list.jsp에서

<c:forEach var="colList" items="${list}" varStatus="num">
	<tr>
		<td>${pageVO.total-((pageVO.page-1)*pageVO.amount)-num.count+1}</td>
		<td class="tit_notice"><a href="notice_view?tno=${colList.tno}">${colList.title}</a></td>
		<td>${colList.hit}</td>
		<td><fmt:formatDate value="${colList.regdate}" pattern="yyyy-MM-dd" /></td>
	</tr>
</c:forEach>

로, 글의 번호를 total의 끝에서부터 첫번째까지 준다.

limit이 사용되었으니 page와 amount에 따라 지정한 만큼 반복문이 돌아간다.

 

 

페이징처리

<div class="pagination">
	<!-- 5.맨처음으로 -->
	<a href="notice_list?page=1&amount=${pageVO.amount}" class="firstpage  pbtn"><img src="${pageContext.request.contextPath}/resources/img/btn_firstpage.png" alt="첫 페이지로 이동"></a>
	
    <!-- 3.이전페이지네이션 -->
	<c:if test="${pageVO.prev }">
		<a href="notice_list?page=${pageVO.start-1}&amount=${pageVO.amount}" class="prevpage  pbtn"><img src="${pageContext.request.contextPath}/resources/img/btn_prevpage.png" alt="이전 페이지로 이동"></a>
	</c:if>
	
    <!-- 1.페이지네이션 -->
	<c:forEach var="num" begin="${pageVO.start}" end="${pageVO.end}">
		<a href="notice_list?page=${num}&amount=${pageVO.amount}"><span class="pagenum ${pageVO.page==num?'currentpage':''}">${num}</span></a>
	</c:forEach>
	
    <!-- 2.다음페이지네이션  -->
	<c:if test="${pageVO.next }">
		<a href="notice_list?page=${pageVO.end+1}&amount=${pageVO.amount}" class="nextpage  pbtn"><img src="${pageContext.request.contextPath}/resources/img/btn_nextpage.png" alt="다음 페이지로 이동"></a>
	</c:if>
	
    <!-- 4.맨마지막으로 -->
	<a href="notice_list?page=${pageVO.realEnd}&amount=${pageVO.amount}" class="lastpage  pbtn"><img src="${pageContext.request.contextPath}/resources/img/btn_lastpage.png" alt="마지막 페이지로 이동"></a>
</div>

 

클래스 currentpage는 조회하는 페이지와 반복문의 num이 같을 때만 주면 됨.
->삼항연상자로 부여

이전버튼과 다음버튼 이동은 PageVO.prev와 next의 조건에 맞춰 활성화되거나 비활성화된다. <c:if>문을 통해.

->페이지네이션의 첫번째인 start는 1,11,21,31...로 증가된다. 1일 때는 앞으로 이동하기가 없다.

->페이지의 끝인 realEnd가 페이지네이션의 마지막인 end보다 작으면 끝부분임. 다음으로 이동하기가 없다.

버튼 a링크에서, amount는 pageVO.amount로 준다. 고정값10으로 주는 게 아님.

 

 

 

 

 

쿼리스트링에 페이지와 amount가 있다. 컨트롤러에서 값을 받아 맞는 내용을 화면에 그리는 것. 버튼은 현 위치에 따라 활성화/비활성화