[Spring] 19.SpringProject-페이징 처리(1)




페이징 처리 - 영속 계층, 비즈니스 계층



지금까지의 저희 게시물 목록 페이지(listAll.jsp)를 보면 전체 게시물이 한 페이지에 모두 표현되었습니다!


image



보기에 정말 불편하죠! 또 매번 my sql 서버에서 전체 데이터를 조회해오는 것도 너무 부담입니다!

그래서 한 페이지에 일정한 게시물만 표현되는 페이징 기능을 저희 프로젝트에 추가해보겠습니다!


image




일단 위의 URI 를 보면 page와 perPageNum가 있죠!

page는 현재 페이지를 나타내고, perPageNum은 페이지 당 보여지는 게시물의 개수를 의미합니다~!

이제부터 천천히 구현해볼까요?


Mapper 추가



만약 현재 페이지 page=3 이고, 페이지 당 보여지는 게시물의 개수 perPageNum=10 라고 한다면,

my sql 서버에 있는 board 테이블에서 21번째 데이터부터 10개를 조회해와야합니다!

my sql에서는 limit 구문을 이용하면 됩니다!


select
...
where xxx
order by xxx
limit 시작데이터, 데이터의 개수




시작데이터는 0부터 시작합니다~!

따라서 select 결과 중 21번째 데이터부터 10개 데이터를 가져오려면 다음과 같이 작성합니다.


-- select 결과 중 21번째 데이터부터 10개 데이터를 가져옴


select *
from board
order by bno desc, regdate desc
limit 20,10




이를 이용해서 BoardMapper에 다음 SQL문을 추가해줍니다!


<!-- boardMapper.xml -->

...

<!-- 일정 페이지 씩 조회 -->
<select id="listPage" resultType="BoardVO">
	select *
	from board
	where bno > 0
	order by bno desc, regdate desc
	limit #{pageStart}, #{perPageNum}
</select>



위에서 pageStart는 (page-1)*perPageNum 입니다!


Criteria 생성



위의 Mapper에서 추가할 때 봤듯이 전체 목록을 페이지 별로 나눠서 보여주려면, page와 perPageNum 값이 필요합니다.

즉, BoardController에서 페이징 기능을 수행하는 메서드는 page와 perPageNum를 매개변수로 받아야하는데요~!


//BoardController.java

package com.gguri.swp.controller;

...

@Controller
@RequestMapping("/board/*")
public class BoardController {
	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	@Inject
	private BoardService service;
	
	...
	
    //페이징 기능으로 목록을 보여주는 메서드
	@RequestMapping(value = "/listPage", method = RequestMethod.GET)
	public void listPage(@RequestParam("page") int page, 
                         @RequestParam("perPageNum") int perPageNum, 
                         Model model) throws Exception{
		...
	}
}



@RequestParam 으로 매개변수를 받다보니 너무 복잡해집니다! 또, 매개변수가 더 많아진다면 관리하기도 힘들어지구요!


따라서, 파라미터들을 맡아서 관리하는 클래스 Criteria를 만들겠습니다!


//Criteria.java


package com.gguri.swp.domain;

public class Criteria {
	private int page;
	private int perPageNum;
	
	public Criteria() {
		this.page = 1;
		this.perPageNum = 10;
	}
    
	//pageStart를 반환
	public int getPageStart() {
		return (this.page - 1)*perPageNum;
	}

	public int getPage() {
		return page;
	}
	public void setPage(int page) {
		if(page <= 0) {
			this.page = 1;
		}else {
			this.page = page;
		}
	}
	public int getPerPageNum() {
		return perPageNum;
	}
	public void setPerPageNum(int perPageNum) {
		if(perPageNum <=0 || perPageNum > 100) {
			this.perPageNum = 10;
		}else {
			this.perPageNum = perPageNum;
		}
	}
	@Override
	public String toString() {
		return "Criteria [page=" + page + ", perPageNum=" + perPageNum + "]";
	}
}



여기서 주목할 점은 Criteria 메소드 중에 getPageStart() 가 있죠?? 아까 mapper에 추가한 sql문에 있는 limit의 시작 데이터인 pageStart를 계산해서 반환해주는 메소드입니다!


Criteria 클래스를 매개변수로 받으면 아까의 listPage 메서드는 좀 더 간단해집니다!


//BoardController.java

package com.gguri.swp.controller;

...

@Controller
@RequestMapping("/board/*")
public class BoardController {
	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	@Inject
	private BoardService service;
	
	...
	
    //페이징 기능으로 목록을 보여주는 메서드
	@RequestMapping(value = "/listPage", method = RequestMethod.GET)
	public void listPage(Criteria cri, Model model) throws Exception{
		...
	}
}




BoardService 수정



이제 본격적으로 아까 boardMapper에 추가한 쿼리를 실행할 수 있도록 BoardService와 BoardDAO를 수정해보겠습니다.


먼저, 인터페이스 BoardService에 listPage 메소드를 추가해줍니다!


//BoardService.java


package com.gguri.swp.service;
...

public interface BoardService {
	...
    
	//listPage 메소드 추가
	List<BoardVO> listPage(Criteria cri) throws Exception;
}




그리고 BoardService 구현체 BoardServiceImpl에서 listPage를 구현해줍니다!


//BoardServiceImpl.java


package com.gguri.swp.service;
...

@Service
public class BoardServiceImpl implements BoardService{
	@Inject
	private BoardDAO boardDAO;
	
	//listPage 구현
	@Override
	public List<BoardVO> listPage(Criteria cri) throws Exception {
		return boardDAO.listPage(cri);
	}
}




아직 BoardDAO 에는 listPage라는 메소드가 없죠?? 추가하러 가봅시다!


BoardDAO 수정




인터페이스 BoardDAO 에 listPage 메소드를 추가해줍니다!


//BoardDAO.java


package com.gguri.swp.persistence;
...

public interface BoardDAO {
	... 
    //listPage 메소드 추가
	 List<BoardVO> listPage(Criteria cri) throws Exception;
}




그 다음에 BoardDAO 구현체 BoardDAOImpl에서도 listPage를 구현해줍니다!


//BoardDAOImpl.java


package com.gguri.swp.persistence;
...

@Repository
public class BoardDAOImpl implements BoardDAO{
	@Inject
	private SqlSession session;
	
	...
	private static String LISTPAGE = NS + ".listPage";
	
    ...
    //listPage 구현
	@Override
	public List<BoardVO> listPage(Criteria cri) throws Exception {
		return session.selectList(LISTPAGE,cri);
	}
}




위의 listPage에서 session은 아까 boardMapper에 추가한 sql문을 실질적으로 실행시킵니다.


<!-- boardMapper.xml -->

<select id="listPage" resultType="BoardVO">
	select *
	from board
	where bno > 0
	order by bno desc, regdate desc
	limit #{pageStart}, #{perPageNum}
</select>



이 때 Criteria를 매개변수로 넘겨주면 pageStart 부분은 Criteria의 getPageStart()를 호출되서 채워집니다!


TEST




페이징 처리를 위해 비즈니스 계층과 영속 계층에 추가해야할 것은 끝났구요~!

서비스객체에서 DAO 객체가 제대로 생성되고 DAO가 아까 추가한 쿼리를 제대로 실행하는지 테스트를 해보겠습니다!

page=1 이고 perPageNum=10 일 때 과연 mysql 서버에서 제대로 데이터를 가져올까요?


//ServiceTest.java


package com.gguri.swp;

...

//Runner 클래스(테스트 메소드를 실행하는 클래스) 를 SpringJUnit4ClassRunner로 함
@RunWith(SpringJUnit4ClassRunner.class)
//location 속성 경로에 있는 xml 파일을 이용해서 스프링이 로딩됨
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/**/root-context.xml")
public class ServiceTest {
	@Inject
	private BoardService service;
	private static Logger logger = LoggerFactory.getLogger(ServiceTest.class);
	
    ...
    
	@Test
	public void listPageTest() throws Exception{
		Criteria cri = new Criteria();
		cri.setPage(1);
		cri.setPerPageNum(10);
		List<BoardVO> boards = service.listPage(cri);
		for (BoardVO board : boards) {
			logger.info(board.getBno()+ ":" + board.getTitle());
		}		
	}
}




image



에러없이 잘 실행되었네요!