Spring/스프링 프로젝트

MySQL이용 시의 페이징2 [VO를 통한 구현]

어굴애 2021. 7. 21. 01:34

게시판이 하나 이상이고 페이징이 필요할 경우 각각의 페이징 처리코드를 view, controller, service에 분산해 처리하기 보다는,

최소한의 코드만을 controller에 남겨두고 VO를 통해 대부분의 연산을 처리하여 재사용 가능하게 할 필요가 있다.

 

 

MySQL에서 페이징 처리를 하기 위해 필요한 개념 [offset, limit]과 연산에 대한 자세한 설명은 이전의 포스팅이 다루고 있다.

2021.07.16 - [Spring/스프링 프로젝트] - MySQL이용 시의 페이징 [개별구현]

 

 


VIEW

 

<div class="indexer align-right">
	<div>
		<span class="text-imp text-strong">${pg.page}</span>
		/ ${pg.lastNum} pages
	</div>
</div>
<div class="align-center pager">
	<div>
		<c:if test="${pg.startNum>1}">
			<a href="?p=${pg.startNum-1}" class="btn btn-next">이전</a>
		</c:if>
		<c:if test="${pg.startNum<=1}">
			<span class="btn btn-prev" onclick="alert('이전 페이지가 없습니다.');">이전</span>
		</c:if>
	</div>
	<ul class="pages center">
		<c:forEach var="i" begin="0" end="4">
		<c:if test="${(pg.startNum+i) <= pg.lastNum}">
			<li><a class="${(pg.page==(pg.startNum+i))?'imp':''} bold" href="?p=${pg.startNum+i}">${pg.startNum+i}</a></li>
		</c:if>
		</c:forEach>
	</ul>
	<div>
		<c:if test="${pg.startNum+5<=pg.lastNum}">
			<a href="?p=${pg.startNum+5}" class="btn btn-next">다음</a>
		</c:if>
		<c:if test="${pg.startNum+5>pg.lastNum}">
			<span class="btn btn-next" onclick="alert('다음 페이지가 없습니다.');">다음</span>
		</c:if>
	</div>
</div>

페이징 개별 처리 시 lastNum, startNum을 구하는 연산이 view단에서 이루어졌으나

VO를 사용할 때는 해당 코드를 VO에 위임하기 때문에 뷰단은 다소 단순해진다.

 

페이징과 직접적으로 관련된 데이터를 구하는 작업은 이미 서버단에서 이루어지고 있다.

view단에선 이미 컨트롤러에서 담아준 pg 모델변수의 각각 속성 값을 꺼내 사용하면 그만이다.

 

다음과 같은 세 개의 속성이 사용된다.

1) page : 현재페이지

2) lastNum : 총 자료의 개수에 따른 마지막 페이지번호 (하나로 고정)

3) startNum : 해당 블럭에서 첫번째 페이지 번호 (5개씩 화면 출력시 1, 6, 11, 16, ...)

 

각각의 div가 어떤 역할을 하는지에 대한 설명은 이미 다룬 적 있기 때문에 해당 링크로 대신한다.

2021.07.16 - [Spring/스프링 프로젝트] - MySQL이용 시의 페이징 [개별구현]

 

 

 


Controller

 

@GetMapping("vcList")
public String vacReqlist(HttpSession session, Model model, @RequestParam(name="p", defaultValue="1") int page) {
	String emp_id = session.getAttribute("id").toString();
	int cnt = vacationService.cntwoff(emp_id);
	model.addAttribute("cntwoff", cnt);
	
    //페이징 처리 1단계 : 쿼리 실행 시 가져오는 레코드 수 구하기 (count함수 활용)
	int cntReq = vacationService.cntMyReq(emp_id);
	model.addAttribute("cntReq", cntReq);
    
	//페이징 처리 2단계 : page VO를 통해 필요한 정보 도출 (view : page, startNum, lastNum | mapper : offset, limit)
	Page p = new Page(page,cnt);
	model.addAttribute("pg", p);
	
    //페이징 처리 3단계 : offset, limit값을 파라미터로 넘겨주어 쿼리 실행
	List<Vacation> vac = vacationService.listVacReq(emp_id, p.getOffset(), p.getScalePerPage());
	model.addAttribute("vacList",vac);
		
	model.addAttribute("annday", vacationService.getVacinfo(emp_id));
	return "emp.vacation.vcList";
	}

 

컨트롤러에서 페이징 처리를 위해 수행해야 할 작업은 크게 세 가지로 요약된다.

  • 전체 데이터의 개수 구하기
  • Page 객체 생성
  • 페이징 처리된 데이터 리스트 구하기

 

1. 가져올 전체 데이터의 개수를 구하기 [페이징 처리 이전 반환되는 행의 개수]

 

Page 객체를 생성하는 시점(생성자 호출)에 페이징과 관련된 모든 연산을 처리해 코드를 단순화하고 싶다면,

먼저 페이징이 처리되지 않았을 때 전체 데이터의 개수를 구할 필요가 있다.

단순히 전체 데이터를 가져오는 쿼리를 count 집계함수를 이용하여 살짝 수정한 뒤 구한다.

 

 

전체 데이터를 가져오는 쿼리 [페이징 이전]

select emp_id, emp_name, emp_dept, emp_tel from emp where emp_id = #{emp_id} order by emp_id

 

전체 데이터의 개수를 가져오는 쿼리 [페이징 이전]

select count(emp_id) from emp where emp_id = #{emp_id} order by emp_id

 

 

 

2. Page객체 생성 [ 페이징 처리에 필요한 데이터 초기화 ]

 

Page객체는 해당 페이지에 맞는 자료만을 가져오기 위한 서비스 함수(최종적으로 쿼리)를 실행할 때 파라미터로 넘겨주어 사용되고, 모델 변수에 담아 view단에서 한번 더 사용될 예정이다. 

 

Page 객체를 생성할 때 view단에서 컨트롤러로 넘어온 page 변수(현재 페이지번호), 앞서 구한 전체 데이터의 개수를 담은 변수cntRows를 파라미터로 넘겨준다. Page 클래스에는 다음과 같은 명시적 생성자가 선언되어 있다.

 

	public Page(int page, double cntRows) {
		this.page = page;
		this.offset =  0+(page-1)*scalePerPage;
		this.lastNum = (int) Math.ceil(cntRows/scalePerPage); 
		this.cntRows = cntRows;
		this.startNum = page-(page-1)%scalePerBlock; 
	}
  • offset, startNum을 구하는 식에 대한 설명은 이전 포스팅에 있기에 생략한다. offset의 개념 관련해서는 바로 아래서 설명한다.
  • cntRows는 double로 선언된다. 
    • lastNum(마지막 페이지 번호 ex.72개 자료라면 8페이지)를 구하기 위해 올림함수 ceil을 사용하는데,  필요한 값은 나머지를 포함한 실수값이다. 이를 위해 cntRows/scalePerPage 중 하나는 실수값을 가져야 한다.
    • lastNum은 정수형이기 때문에 연산하여 나온 값을 다시 int로 강제형변환한다.

 

 

 

3.  페이징 처리된 데이터 리스트 구하기

 

제한된 데이터를 가져오는 쿼리 [페이징 이후]

select emp_id, emp_name, emp_dept, emp_tel from emp where emp_id = #{emp_id} order by emp_id
limit #{offset}, #{limit}

SQL 쿼리에서 페이징 작업은 단순히 limit #{offset}, #{limit} 만을 추가하면 완료된다.

offset은 현재 페이지 이전까지 몇 개의 자료가 생략되었는지를 의미하는 개념이다.

limit는 하나의 페이지에서 보여줄 자료의 개수를 담는 변수이다.

- Page VO에서는 scalePerPage라는 이름을 사용하며, 기본값을 필드에서 초기화했다[아래 VO 코드 참고].

 

하나의 페이지가 10개의 자료를 출력한다고 가정할 때,

1페이지라면 0개, 2페이지라면 10개(1~10), 3페이지라면 20개(1~20), 4페이지라면 30개(1~30)의 자료를 순서대로 스킵하고, 그 뒤의 10개의 페이지를 보여주는 식이다.

 

Page객체를 생성할 때 이미 offset과 limit값은 정해졌다.

단순히 Page객체와 다른 조건을 설정하기 위한 변수를 파라미터로 하는 서비스를 호출한다.

 

Page VO

package com.clockOn.web.entity;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class Page {
	private int page; //현재페이지번호
	private int scalePerPage = 10; //페이지당 보여줄 자료의 개수 (10개로 고정)
	private int scalePerBlock = 5; //블럭당 보여줄 페이지의 최대 개수 (5개)
	private int offset; //건너뛸 자료의 개수 (1페이지 0, 2페이지 10개)
	private int lastNum; //마지막 페이지 번호
	private double cntRows; //쿼리 결과 자료의 개수
	private int startNum; //현재 블럭에서 시작 페이지 번호 (1, 6, 11, 16, ...)
//	private int lastNum; //현재 블럭에서 마지막 페이지 번호 (5, 10, 15, ...)
	
	
	//Controller에서 Get요청 처리 시 가장 먼저 수행할 작업(생성자)
	public Page(int page, double cntRows) {
		this.page = page;
		this.offset =  0+(page-1)*scalePerPage;
		this.lastNum = (int) Math.ceil(cntRows/scalePerPage); 
		this.cntRows = cntRows;
		this.startNum = page-(page-1)%scalePerBlock; 
	}
	
}

페이지당 보여줄 자료의 개수는 주로 고정되어 있을 것이기 때문에 필드 선언 시에 초기화했다.

그러나 무한스크롤을 이용한 페이징을 추가 구현할 때 setter를 통해 해당 값들을 조정하여 사용할 수 있다.

현재 블럭에서 마지막 페이지 번호는 MySql을 사용하여 페이징을 구현할 때 필요없는 변수이기 때문에 주석처리했다.

 

 

ServiceImpl

	@Override
	public List<Vacation> listVacReq(String emp_id, int offset, int limit) {
		return vacationDao.listVacReq(emp_id, offset, limit);
	}
    
    @Override
	public int cntMyReq(String emp_id) {
		return vacationDao.cntMyReq(emp_id);
	}

 

DAO

public List<Vacation> listVacReq(@Param("emp_id") String emp_id, 
		@Param("offset") int offset, @Param("limit") int limit);
        
public int cntMyReq(String emp_id);

 

DAOMapper.xml

<!-- 직원 : 특정 직원의 모든 요청 불러오기 -->
<select id="listVacReq" parameterType="String" resultType="com.clockOn.web.entity.vacation.Vacation">
	select holi_rid, holi_period, holi_cnt, holi_res, holi_code,
		case when holi_state= '0' then '대기' when holi_state ='1' then '승인' when holi_state ='2' then '거절' end as 'holi_state', holi_evi
	from holiday where emp_id=#{emp_id} 
    order by holi_rid desc limit ${offset}, ${limit}
</select>

<!--직원 : 특정 직원의 모든 요청 수 불러오기 -->
<select id="cntMyReq" resultType="int" parameterType="String">
	select count(holi_rid) from holiday where emp_id = #{emp_id}
</select>