2024. 3. 31. 14:44ㆍ· BACK-END/└ Spring Boot
환경 : Spring Tools Suite4
추가 설명 : 페이징을 위한 클래스를 따로 만드는 이유를 이해하기 위한 이해 과정입니다.
홈페이지에 보면 보여줄 내용이 많을 경우 페이징(Paging)이라는 처리를 한다.
페이지를 나누어 보여주고 이동할 수 있는 버튼을 만들어두는 형태로, 이를 페이지 네비게이터(Page Navigator)라고 부른다.
[이전] 1 2 3 4 5 6 7 8 9 10 [다음]
페이지 네비게이터는 다음과 같은 (계산)규칙을 가지고 있다.
- 현재 내가 몇 페이지에 있느냐에 따라 첫 번호와 마지막 번호가 결정된다
- 1페이지부터 10페이지 사이에서는 첫 번호는 1이다
- 1페이지부터 10페이지 사이에서는 마지막 번호는 10이다
- 11페이지부터 20페이지 사이에서는 첫 번호는 11이다.
- 11페이지부터 20페이지 사이에서는 마지막 번호는 20이다
- 이후에도 계속 페이지가 증가하면 그에 맞는 첫번호와 마지막번호가 계산되어야 한다.
- Paging.java
public class Paging {
public static void main(String[] args) {
//입력
int page = 275;
int size = 5; // 보여줄 번호의 개수
//계산
int begin = (page - 1) / size * size +1;
int end = begin + (size - 1);
//출력
System.out.println("시작번호 : " + begin);
System.out.println("종료번호 : " + end );
}
}
(+) 275페이지에 위치할 때 첫 번호와 마지막 번호를 출력하는 코드
[ jsp에서 네비게이터를 어떻게 보여주는 지에 대한 이해 과정 ]
다른 코드는 하단에 첨부하였다.
10개씩 보여주는 페이지 네비게이터를 만드는 코드
1. 10개의 페이지에 직접 페이지 번호를 붙인 링크를 할당한다.
<%--네비게이터 --%>
<h2>
<
<a href="list?page=1">1</a>
<a href="list?page=2">2</a>
<a href="list?page=3">3</a>
<a href="list?page=4">4</a>
<a href="list?page=5">5</a>
<a href="list?page=6">6</a>
<a href="list?page=7">7</a>
<a href="list?page=8">8</a>
<a href="list?page=9">9</a>
<a href="list?page=10">10</a>
>
</h2>
(+) 단점 : 페이지가 더 늘어난다면, 계속 코드를 고쳐줘야한다.
10개의 리스트씩 보여주는 페이지만 만들어진다. (한 페이지 당 보여질 게시글의 개수를 10개만 지정 가능)
2. 페이지의 총 개수를 자유롭게 만들어주기 (forEach문 활용)
<%--네비게이터 --%>
<h2>
<
<c:forEach var = "i" begin="${beginBlock}" end="${endBlock}" step="1">
<a href="list?page=${i}">${i}</a>
</c:forEach>
>
</h2>
(+) 위와 동일한 역할을 하는 자바 코드 : for(int i=beginBlock; i<=endBlock; i++) {}
3. 현재 페이지에는 링크 태그를 주지 않기 위해 c:choose 문을 활용
<%--네비게이터 --%>
<h2>
<
<%--for(int i=beginBlock; i<=endBlock; i++) {} --%>
<c:forEach var = "i" begin="${beginBlock}" end="${endBlock}" step="1">
<%-- 다른 페이지일 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${page == i}">${i}</c:when>
<c:otherwise>
<a href="list?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
>
</h2>
(+) 다른 페이지인 경우만 링크를 제공. 현재 페이지는 링크를 보여주지 않음
(+) 페이지 값과 i의 값이 같은 경우는 현재 페이지 이므로 링크 태그 해제
4. 이전과 다음 구현
<%--네비게이터 --%>
<h2>
<a href="list?page=${beginBlock-1}"><이전</a>
<%--for(int i=beginBlock; i<=endBlock; i++) {} --%>
<c:forEach var = "i" begin="${beginBlock}" end="${endBlock}" step="1">
<%-- 다른 페이지일 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${page == i}">${i}</c:when>
<c:otherwise>
<a href="list?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<a href="list?page=${endBlock+1}">다음></a>
</h2>
5. 이전, 다음이 없는 경우엔 클릭되지 않도록 설정
<%--네비게이터 --%>
<h2>
<%-- 이전이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${beginBlock == 1}"><이전</c:when>
<c:otherwise>
<a href="list?page=${beginBlock-1}"><이전</a>
</c:otherwise>
</c:choose>
<%--for(int i=beginBlock; i<=endBlock; i++) {} --%>
<c:forEach var = "i" begin="${beginBlock}" end="${endBlock}" step="1">
<%-- 다른 페이지일 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${page == i}">${i}</c:when>
<c:otherwise>
<a href="list?page=${i}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<%-- 다음이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${endBlock >= totalPage}">다음></c:when>
<c:otherwise>
<a href="list?page=${endBlock+1}">다음></a>
</c:otherwise>
</c:choose>
</h2>
(+) 이전의 경우 페이지가 1보다 작은 경우. 작동하지 않아야 함.
(+) 다음의 경우 페이지가 끝 페이지 보다 작은 경우. 작동하지 않아야 함.
6. 한 페이지 당 보여지는 게시글 개수 추가 (size)
<%--네비게이터 --%>
<h2>
<%-- 이전이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${beginBlock == 1}"><이전</c:when>
<c:otherwise>
<a href="list?page=${beginBlock-1}&size=${size}"><이전</a>
</c:otherwise>
</c:choose>
<%--for(int i=beginBlock; i<=endBlock; i++) {} --%>
<c:forEach var = "i" begin="${beginBlock}" end="${Math.min(totalPage, endBlock)}" step="1">
<%-- 다른 페이지일 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${page == i}">${i}</c:when>
<c:otherwise>
<a href="list?page=${i}&size=${size}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<%-- 다음이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${endBlock >= totalPage}">다음></c:when>
<c:otherwise>
<a href="list?page=${endBlock+1}&size=${size}">다음></a>
</c:otherwise>
</c:choose>
</h2>
7. 검색 시에도 유지되도록 설정(검색어를 링크에 넣어주기)
<%--네비게이터 --%>
<h2>
<%-- 이전이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${beginBlock == 1}"><이전</c:when>
<c:otherwise>
<a href="list?page=${beginBlock-1}&size=${size}&column=${param.column}&keyword=${param.keyword}"><이전</a>
</c:otherwise>
</c:choose>
<%--for(int i=beginBlock; i<=endBlock; i++) {} --%>
<c:forEach var = "i" begin="${beginBlock}" end="${Math.min(totalPage, endBlock)}" step="1">
<%-- 다른 페이지일 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${page == i}">${i}</c:when>
<c:otherwise>
<a href="list?page=${i}&size=${size}&column=${param.column}&keyword=${param.keyword}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<%-- 다음이 있을 경우만 링크를 제공 --%>
<c:choose>
<c:when test="${endBlock >= totalPage}">다음></c:when>
<c:otherwise>
<a href="list?page=${endBlock+1}&size=${size}&column=${param.column}&keyword=${param.keyword}">다음></a>
</c:otherwise>
</c:choose>
</h2>
(+)Math.min(totalPage, endBlock) : 전체 페이지 수와 맨 끝 페이지 수(임의로 10개씩 나누어진 수)의 최소값을 구하여, 마지막 페이지 수를 구함.
단, 검색 시에도 페이징을 하기 위해 코드를 개조하다보면, 일반 목록 출력에 오류가 발생한다.
따라서 아래와 같이 일부 코드를 추가해줘야 한다.
- Controller.java
//게시판에서는 empty string으로 전달되는 파라미터를 null로 간주하도록 설정(이 컨트롤러 내에서 적용됨)
//@InitBinder 설정으로 구현
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); //문자열의 클래스는 new StringTrimmerEditor의 힘을 빌려라.
}
(+) @IntiBinder 어노테이션을 설정해준다.
공백을 null로 간주하도록 설정해주는 기능이고, 이는 해당 컨트롤러 내에서만 적용된다.
int count = isSearch ? boardDao.count(column, keyword) : boardDao.count();
(+) count 변수에 삼항연산자 조건을 넣어준다.
- Dao.java
//카운트 - 목록일 경우와 검색일 경우를 각각 구현
public int count() {
String sql ="select count(*) from board";
return jdbcTemplate.queryForObject(sql, int.class); //낱개 데이터 조회시 좋고, 매퍼 없이도 가능
}
public int count(String column, String keyword) {
String sql = "select count(*) from board "
+ "where instr("+column+", ?) > 0";
Object[] data = {keyword};
return jdbcTemplate.queryForObject(sql, int.class, data);
}
(+) 위의 삼항연산자에 넣은 count() 메소드
[ 기타 코드 ]
- Controller.java
// 게시글 목록, 검색 + 페이징
@RequestMapping("/list")
public String list(@RequestParam(required=false) String column,
@RequestParam(required=false) String keyword,
@RequestParam(required=false, defaultValue = "1") int page,
@RequestParam(required=false, defaultValue="10") int size,
Model model) {
boolean isSearch = column != null && keyword != null;
/*
화면에 네비게이터를 보여주는 데 필요한 값들을 계산
- blockSize : 화면에 표시할 네비게이터 개수 (10으로 설정)
- beginBlock : 네비게이터의 처음 숫자, (페이지-1) / 10 * 10 + 1 (10은 blockSize)
- endBlock : 네비게이터의 마지막 숫자, (페이지-1) / 10 * 10 + 10 (10은 blockSize)
- count: 게시글 개수
- totalPage : 전체 페이지 개수
*/
int blockSize = 10;
int beginBlock = (page-1) / blockSize * blockSize + 1;
int endBlock = (page-1) / blockSize * blockSize + blockSize;
model.addAttribute("beginBlock", beginBlock); //네비게이터 시작 번호
model.addAttribute("endBlock", endBlock); //네비게이터 종료 번호
model.addAttribute("page", page); //현재 페이지 번호
// int count = boardDao.count();
int count = isSearch ? boardDao.count(column, keyword) : boardDao.count();
int totalPage = (count - 1) / size + 1; //한 페이지 당 보여지는 페이지 개수가 달라지면 총 페이지 수가 달라지기 때문에 size가 totalPage에 영향을 준다. 따라서 size값을 삽입 (blockSize 아님)
model.addAttribute("count", count); //게시글 개수
model.addAttribute("totalPage", totalPage); // 총 페이지 수
model.addAttribute("size", size); // 현재 게시글 표시 개수
//model.addAttribute("page", page); //현재 페이지 번호 필요, 위에서 보내줘서 주석처리
if(isSearch) {
model.addAttribute("list", boardDao.selectListByPaging(page, size, column, keyword));
}
else {
//model.addAttribute("list", boardDao.selectList());
model.addAttribute("list", boardDao.selectListByPaging(page, size));
}
return "/WEB-INF/views/board/list.jsp";
}
- Dao.java
//목록+페이징
/* - page는 현재 조회할 페이지 번호
* - size는 조회할 페이지의 출력 개수
* - 위 두개를 이용하여 시작행(beginRow)과 종료행(endRow)를 계산 */
public List<BoardDto> selectListByPaging(int page, int size){
int endRow = page * size;
int beginRow = endRow - (size-1);
String sql = "select * from ("
+ "select rownum rn, TMP.* from ("
+ "select "
+ "board_no, board_title, board_writer, "
+ "board_wtime, board_etime, board_readcount "
+ "from board order by board_no desc"
+ ")TMP"
+ ") where rn between ? and ?";
Object[] data = {beginRow, endRow};
return jdbcTemplate.query(sql, boardListMapper, data);
}
//검색+페이징
public List<BoardDto> selectListByPaging(int page, int size, String column, String keyword) {
int endRow = page * size;
int beginRow = endRow - (size-1);
String sql = "select * from ("
+ "select rownum rn, TMP.* from ("
+ "select "
+ "board_no, board_title, board_writer, "
+ "board_wtime, board_etime, board_readcount "
+ " from board where instr("+column+", ?) > 0 "
+ "order by board_no desc"
+ ")TMP"
+ ") where rn between ? and ?";
Object[] data = {keyword, beginRow, endRow};
return jdbcTemplate.query(sql, boardListMapper, data);
}
개인 공부 기록용입니다:)
'· BACK-END > └ Spring Boot' 카테고리의 다른 글
[ Spring MVC ] MVC Patterns (1) | 2024.03.25 |
---|---|
[ Spring ] @Configuration Annotation (0) | 2024.03.24 |
[ Spring ] @RequestMapping Annotation (0) | 2024.02.17 |
[ Spring ] @RequestParam Annotation (0) | 2024.02.17 |
[ Spring ] @ModelAttribute Annotation (0) | 2024.02.15 |