Luver Duck 2022. 9. 20. 22:16

페이징(Paging)

 

 

 

1) 현재 페이지 게시글 rownum의 시작과 끝 번호 관련

- Top N Query를 이용하여 작성일 기준 내림차순(최신순) 조회 결과의 갯수를 제한하여 View에 표시한다

- Top N Query의 rownum rn의 범위에 들어갈 바인드 변수의 값은 startRow()와 endRow() 메소드의 반환값으로 한다

- 현재 페이지의 첫 게시글 rownum과 마지막 게시글 rownum은 각각 startRow(), endRow()의 반환값과 같다

- 현재 페이지의 번호(page)와 페이지에 표시할 게시글의 수(size)에 대하여 startRow, endRow와의 관계는 다음과 같다

- startRow = endRow - (size - 1)

- endRow = p * size

 

** 2)와의 통일성을 위해 startRow를 먼저 구한 후 endRow를 구하는 방법도 있다

- startRow = (p - 1) / size * size + 1

- endRow = startRow + (size - 1)

 

2) 현재 페이지 블럭의 시작과 끝 번호 관련

- 한 페이지에 표시할 블럭의 갯수(블럭 구간의 크기, blockSize)는 10으로 한다

- 현재 페이지에 표시할 블럭의 시작 번호의 값은 startBlock() 메소드의 반환값으로 한다

- 현재 페이지에 표시할 블럭의 끝 번호의 값은 endBlock() 메소드의 반환값으로 한다

- 현재 페이지에 표시할 블럭의 끝 번호가 될 수 있는 값은 두 종류이며 두 값중 작은 값으로 한다

   i) 다음 블럭 구간이 존재할 경우

      - 마지막 블럭 구간의 끝 블럭 번호가 마지막 블럭 번호(lastBlock)보다 크다

      - 이 때 블럭의 끝 번호 = 현재 페이지에 표시할 블럭의 시작 번호(startBlock)에 (blockSize - 1)을 더한 값으로 한다

   ii) 다음 블럭 구간이 존재하지 않을 경우

      - 마지막 블럭 구간의 끝 블럭 번호가 마지막 블럭 번호(lastBlock)와 같다

      - 이 때 블럭의 끝 번호 = 블럭의 마지막 번호(lastBlock)으로 한다

- 블럭 구간의 크기(blockSize)에 대하여 starBlock, endBlock와의 관계는 다음과 같다

- startBlock = (p - 1) / blockSize * blockSize + 1

- endBlock = Math.min( startBlock + (blockSize - 1), lastBlock() )

 

3) 페이지 블럭 전체의 시작과 끝 번호 관련

- 페이지 블럭 전체의 시작 블럭 번호는 firstBlock() 메소드의 반환값으로 한다

- 페이지 블럭 전체의 끝 블럭 번호는 lastBlock() 메소드의 반환값으로 한다

- 페이지 블럭 전체의 시작 블럭 번호는 1로 한다

- 페이지 블럭 전체의 끝 블럭 번호는 마지막 게시글이 속한 블럭 번호(마지막 페이지 번호)와 같다

- firstBlock = 1

- lastBlock = pageCount

 

4) 페이지 이동시 표시할 블럭의 번호 관련

- 현재 페이지에 표시할 시작 번호 블럭의 이전 블럭 번호는 prevBlock() 메소드의 반환값으로 한다

- 현재 페이지에 표시할 끝 번호 블럭의 다음 블럭 번호는 nextBlock() 메소드의 반환값으로 한다

- 이전 블럭 번호는 현재 페이지에 표시할 시작 번호 블럭의 번호에서 1을 뺀 값이다

- 다음 블럭 번호는 현재 페이지에 표시할 끝 번호 블럭의 번호에서 1을 더한 값이다

- prevBlock = startBlock - 1

- nextBlock = endBlock + 1

 

BoardListSearchVO

1) 현재 페이지 게시글 rownum의 시작과 끝 번호 관련

 

// 1) 현재 페이지 게시글 rownum의 시작과 끝 번호 관련
// 필드
private int p = 1;	// 현재 페이지 번호 (기본값이 1이 되도록)
private int size = 10;	// 페이지에 표시할 게시글의 수

// 메소드
// 현재 페이지 첫 게시글 rownum
public int startRow() {
    return endRow() - (size - 1);
}

// 현재 페이지 마지막 게시글 rownum
public int endRow() {
    return p * size;
}

 

- 필드

int page 현재 페이지 번호 (기본값이 1이 되도록 초기값을 1로 정한다)
int size 페이지에 표시할 게시글의 수

 

- 메소드

int startRow() 현재 페이지의 첫 번째 게시글 rownum을 반환
int endRow() 현재 페이지의 마지막 게시글 rownum을 반환

 

ex) 2페이지에 표시할 게시글

2페이지에 표시할 게시글의 rownum은 11부터 20까지이다

 

2페이지에 대하여 page = 2, size = 10이므로 2페이지의 마지막 게시글 rownum인 20(endRow)과의 관계식은 다음과 같다

20(endRow) = 2(page) * 10(size)

∴ endRow = page * size

 

2페이지에 표시할 게시글의 수(size)와 11(startRow), 20(endRow) 사이의 관계식은 다음과 같다

2페이지에 표시할 게시글의 수(size) = 20(endRow) - 11(startRow) + 1

∴ size = endRow - startRow + 1

 

따라서 2페이지의 첫 번째 게시글 rownum은 다음과 같은 관계식으로 나타낼 수 있다

∴ startRow = endRow - size + 1

∴ startRow = endRow - (size - 1)

 

 

2) 현재 페이지 블럭의 시작과 끝 번호 관련

 

// 2) 현재 페이지 블럭의 시작과 끝 번호 관련
// 현재 페이지에 표시할 블럭의 시작 번호
public int startBlock() {
    return (p - 1) / blockSize * blockSize + 1;
}

// 현재 페이지에 표시할 블럭의 끝 번호
public int endBlock() {
    int value = startBlock() + blockSize - 1;
    return Math.min(value, lastBlock());
}

 

- 필드 없음

 

- 메소드

int startBlock() 현재 페이지에 표시할 블럭의 시작 번호 반환
int endBlock() 현재 페이지에 표시할 블럭의 끝 번호 반환

 

 

3) 페이지 블럭 전체의 시작과 끝 번호 관련

 

// 3) 페이지 블럭 전체의 시작과 끝 번호 관련
// 필드
// 총 게시글의 수 (= 마지막 게시글의 rownum)
private int count;

// 페이지에 표시할 블럭 구간의 크기
private int blockSize = 10; 

// 메소드
// 마지막 게시글이 속한 블럭 번호(마지막 페이지 번호)
public int pageCount() {
    return (count + (size - 1)) / size;
}

// 블럭의 시작 번호(첫 페이지 번호)
public int firstBlock() {
    return 1;
}

// 블럭의 끝 번호 (마지막 페이지 번호 = 마지막 게시글이 속한 블럭 번호)
public int lastBlock() {
    return pageCount();
}

 

- 필드

int count 총 게시글의 수 (= 마지막 게시글의 rownum)
int blockSize 페이지에 표시할 블럭 구간의 크기 (10개의 블럭을 표시할 예정)

 

- 메소드

int pageCount() 마지막 게시글이 속한 블럭 번호(마지막 페이지 번호)
int firstBlock() 블럭의 시작 번호 반환 (첫 페이지 번호)
int lastBlock() 블럭의 끝 번호 반환 (마지막 페이지 번호 = 마지막 게시글이 속한 블럭 번호)

 

 

4) 페이지 이동시 표시할 블럭의 번호 관련

 

// 4) 페이지 이동시 표시할 블럭의 번호 관련
// 이전 블럭의 번호 반환
public int prevBlock() {
    return startBlock() - 1;
}

// 다음 블럭의 번호 반환
public int nextBlock() {
    return endBlock() + 1;
}

// 현재 블럭이 첫 번째 블럭인지의 여부를 반환
public boolean isFirst() {
    return p == 1;
}

// 현재 블럭이 마지막 블럭인지의 여부를 반환
public boolean isLast() {
    return endBlock() == lastBlock();
}

// 이전 블럭 구간이 존재하는지의 여부를 반환
public boolean hasPrev() {
    return startBlock() > 1;
}

// 다음 블럭 구간이 존재하는지의 여부를 반환
public boolean hasNext() {
    return endBlock() < lastBlock();
}

 

- 필드 없음

 

- 메소드

int prevBlock() 이전 블럭의 번호 반환(이전 페이지 번호)
int nextBlock() 다음 블럭의 번호 반환(다음 페이지 번호)
boolean isFirst() 해당 블럭이 시작 번호 블럭인지(첫 페이지인지)의 여부 반환
boolean isLast() 해당 블럭이 끝 번호 블럭인지(마지막 페이지인지)의 여부 반환
boolean hasPrev() 이전 블럭 구간이 존재하는지의 여부 반환
boolean hasNext() 다음 블럭 구간이 존재하는지의 여부 반환

 

 

5) 검색 조회시에도 페이징을 유지할 수 있도록 주소 뒤에 붙일 Query String 생성

- p(페이지 번호)를 제외한 나머지 항목들에 대한 파라미터명과 그 값을 반환

- 검색 조회일 경우 파라미터명인 size(페이지에 표시할 게시글의 수), type(검색 분류), keyword(검색어)와 그 값을 반환

- 전체 조회일 경우 size(페이지)와 그 값을 반환

 

// 5) 검색 조회시에도 페이징을 유지할 수 있도록 주소 뒤에 붙일 Query String을 반환
// - p(페이지 번호)를 제외한 나머지 항목들에 대한 파라미터명과 그 값을 반환
public String parameter() {
    if(isSearch()) { // 검색 조회일 경우
        return "size=" + size + "&type=" + type + "&keyword=" + keyword;
    }
    else { // 전체 조회일 경우
        return "size=" + size;
    }
}

 

- 필드 없음

 

- 메소드

String parameter() 조회 유형(검색 조회/전체 조회)에 따라 다른 Query String을 반환

 

 

BoardListSearchVO (완성)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoardListSearchVO {

	// 필드
	private String type;	// 검색 분류
	private String keyword;	// 검색어
	
	// 메소드 - 조회 유형이 검색 조회인지 여부 반환
	public boolean isSearch() {
		return type != null && keyword != null;
	}
	
	// 1) 현재 페이지 게시글 rownum의 시작과 끝 번호 관련
	// 필드
	private int p = 1;	// 현재 페이지 번호 (기본값이 1이 되도록)
	private int size = 10;	// 페이지에 표시할 게시글의 수
	
	// 메소드
	// 현재 페이지 첫 게시글 rownum
	public int startRow() {
		return endRow() - (size - 1);
	}
	
	// 현재 페이지 마지막 게시글 rownum
	public int endRow() {
		return p * size;
	}
	
	// 2) 현재 페이지 블럭의 시작과 끝 번호 관련
	// 현재 페이지에 표시할 블럭의 시작 번호
	public int startBlock() {
		return (p - 1) / blockSize * blockSize + 1;
	}
	
	// 현재 페이지에 표시할 블럭의 끝 번호
	public int endBlock() {
		int value = startBlock() + blockSize - 1;
		return Math.min(value, lastBlock());
	}
	
	// 3) 페이지 블럭 전체의 시작과 끝 번호 관련
	// 필드
	// 총 게시글의 수 (= 마지막 게시글의 rownum)
	private int count;
	
	// 페이지에 표시할 블럭 구간의 크기
	private int blockSize = 10; 
	
	// 메소드
	// 마지막 게시글이 속한 블럭 번호(마지막 페이지 번호)
	public int pageCount() {
		return (count + (size - 1)) / size;
	}
	
	// 블럭의 시작 번호(첫 페이지 번호)
	public int firstBlock() {
		return 1;
	}
	
	// 블럭의 끝 번호 (마지막 페이지 번호 = 마지막 게시글이 속한 블럭 번호)
	public int lastBlock() {
		return pageCount();
	}
	
	// 4) 페이지 이동시 표시할 블럭의 번호 관련
	// 이전 블럭의 번호 반환
	public int prevBlock() {
		return startBlock() - 1;
	}
	
	// 다음 블럭의 번호 반환
	public int nextBlock() {
		return endBlock() + 1;
	}
	
	// 현재 블럭이 첫 번째 블럭인지의 여부를 반환
	public boolean isFirst() {
		return p == 1;
	}
	
	// 현재 블럭이 마지막 블럭인지의 여부를 반환
	public boolean isLast() {
		return endBlock() == lastBlock();
	}
	
	// 이전 블럭 구간이 존재하는지의 여부를 반환
	public boolean hasPrev() {
		return startBlock() > 1;
	}
	
	// 다음 블럭 구간이 존재하는지의 여부를 반환
	public boolean hasNext() {
		return endBlock() < lastBlock();
	}
	
	// 5) 검색 조회시에도 페이징을 유지할 수 있도록 주소 뒤에 붙일 Query String을 반환
	// - p(페이지 번호)를 제외한 나머지 항목들에 대한 파라미터명과 그 값을 반환
	public String parameter() {
		if(isSearch()) { // 검색 조회일 경우
			return "size=" + size + "&type=" + type + "&keyword=" + keyword;
		}
		else { // 전체 조회일 경우
			return "size=" + size;
		}
	}
}

 

- 필드

String typ 검색 분류
String keyword 검색어
int p 현재 페이지 번호 (기본값이 1이 되도록)
int size 페이지에 표시할 게시글의 수
int count 총 게시글의 수 (= 마지막 게시글의 rownum)
int blockSize 페이지에 표시할 블럭 구간의 크기

 

- 메소드

boolean isSearch() 조회 유형이 검색 조회인지의 여부 반환
int startRow() 현재 페이지 첫 게시글 rownum 반환
int endRow() 현재 페이지 마지막 게시글 rownum 반환
int startBlock() 현재 페이지에 표시할 블럭의 시작 번호 반환
int endBlock() 현재 페이지에 표시할 블럭의 끝 번호 반환
int pageCount() 마지막 게시글이 속한 페이지 번호 반환 (마지막 페이지 번호)
int firstBlock() 블럭의 시작 번호 반환 (첫 페이지 번호)
int lastBlock() 블럭의 끝 번호 반환 (마지막 페이지 번호 = 마지막 게시글이 속한 블럭 번호)
int prevBlock() 이전 블록의 번호 반환(이전 페이지 번호)
int nextBlock() 다음 블록의 번호 반환(다음 페이지 번호)
boolean isFirst() 현재 블럭이 시작 번호 블럭인지(첫 페이지인지)의 여부 반환
boolean isLast() 현재 블럭이 끝 번호 블럭인지(마지막 페이지인지)의 여부 반환
boolean hasPrev() 이전 블럭 구간이 존재하는지의 여부 반환
boolean hasNext() 다음 블럭 구간이 존재하는지의 여부 반환
String parameter() 조회 유형(검색 조회/전체 조회)에 따라 다른 Query String을 반환

 

BoardDao

public interface BoardDao {

	// 추상 메소드 - 게시글 조회
	// 3) BoardListSearchVO를 이용한 통합 조회
	List<BoardDto> selectList(BoardListSearchVO vo);
	// - 전체 조회
	List<BoardDto> list(BoardListSearchVO vo);
	// - 검색 조회
	List<BoardDto> search(BoardListSearchVO vo);

	// 추상 메소드 - 전체 조회와 검색 조회 시 총 조회 결과 갯수 (마지막 게시글의 rownum)
	// - 조회 유형에 따른 총 조회 결과 갯수
	int count(BoardListSearchVO vo);
	// - 전체 조회시 총 조회 결과 갯수
	int listCount(BoardListSearchVO vo);
	// - 검색 조회시 총 조회 결과 갯수
	int searchCount(BoardListSearchVO vo); 
}

 

BoardDaoImpl

- 조회 유형 판정과 실행시킬 메소드를 BoardDaoImpl에서 결정하도록 변경

@Repository
public class BoardDaoImpl implements BoardDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;

	// 게시글 조회를 위한 RowMapper
	private RowMapper<BoardDto> mapper = new RowMapper<>() {
		@Override
		public BoardDto mapRow(ResultSet rs, int rowNum) throws SQLException {
			return BoardDto.builder()
					.boardNo(rs.getInt("board_no"))
					.boardWriter(rs.getString("board_writer"))
					.boardTitle(rs.getString("board_title"))
					.boardContent(rs.getString("board_content"))
					.boardWritetime(rs.getDate("board_writetime"))
					.boardUpdatetime(rs.getDate("board_updatetime"))
					.boardRead(rs.getInt("board_read"))
					.boardLike(rs.getInt("board_like"))
					.boardHead(rs.getString("board_head"))
				.build();
		}
	};

	// 추상 메소드 오버라이딩 - 게시글 조회
	// 3) BoardListSearchVO를 이용한 통합 조회
	@Override
	public List<BoardDto> selectList(BoardListSearchVO vo) {
		// 조회 유형
		if(vo.isSearch()) {	// 검색 조회일 경우
			return search(vo);
		}
		else {	// 그렇지 않다면
			return list(vo);
		}
	}
	
	// - 전체 조회
	@Override
	public List<BoardDto> list(BoardListSearchVO vo) {
		String sql = "select * from ("
						+ "select rownum rn, TMP.* from ("
							+ "select * from board order by board_no desc"
						+ ")TMP"
					+ ") where rn between ? and ?";
		Object[] param = {vo.startRow(), vo.endRow()};
		return jdbcTemplate.query(sql, mapper, param);
	}
	
	// - 검색 조회
	@Override
	public List<BoardDto> search(BoardListSearchVO vo) {
		String sql = "select * from ("
						+ "select rownum rn, TMP.* from ("
							+ "select * from board "
							+ "where instr(#1, ?) > 0 "
							+ "order by board_no desc"
						+ ")TMP"
					+ ") where rn between ? and ?";
		sql = sql.replace("#1", vo.getType());
		Object[] param = {vo.getKeyword(), vo.startRow(), vo.endRow()};
		return jdbcTemplate.query(sql, mapper, param);
	}

	// 추상 메소드 - 전체 조회와 검색 조회 시 조회 결과의 총 갯수 (마지막 게시글의 rownum)
	// - 조회 유형에 따른 총 조회 결과 갯수
	@Override
	public int count(BoardListSearchVO vo) {
		if(vo.isSearch()) {	// 검색 조회일 경우
			return searchCount(vo);	// 검색 조회의 조회 결과 총 갯수 반환
		}
		else {	// 그렇지 않을 경우(전체 조회)
			return listCount(vo);	// 전체 조회의 조회 결과 총 갯수 반환
		}
	}

	// - 전체 조회시 조회 결과의 총 갯수
	@Override
	public int listCount(BoardListSearchVO vo) {
		String sql = "select count(*) from board";
		return jdbcTemplate.queryForObject(sql, int.class);
	}

	// - 검색 조회시 조회 결과의 총 갯수
	@Override
	public int searchCount(BoardListSearchVO vo) {
		String sql = "select count(*) from board where instr(#1, ?) > 0";
		sql = sql.replace("#1", vo.getType());
		Object[] param = new Object[] {vo.getKeyword()};
		return jdbcTemplate.queryForObject(sql, int.class, param);
	}
}

 

BoardController

- BoardListSearchVO에는 총 6개의 필드가 있다

String typ 검색 분류
String keyword 검색어
int p 현재 페이지 번호 (기본값을 1로 한다)
int size 페이지에 표시할 게시글의 수
int count 총 게시글의 수
int blockSize 페이지에 표시할 블럭의 수 (블럭 묶음)

- 총 게시글의 수(count)는 BoardDao의 count(BoardListSearchVO vo) 메소드의 반환값으로 설정 (setter)

- 설정이 끝난 vo를 매개변수로 하여 조회 유형(전체 조회/검색 조회)에 따른 조회를 실행한 후 그 결과를 model에 첨부 

 

@Controller
@RequestMapping("/board")
public class BoardController {

	// 의존성 주입
	@Autowired
	BoardDao boardDao;
	
	// 2. 게시글 목록
	// 게시글 목록 Mapping
	@GetMapping("/list")
	public String selectList(Model model, @ModelAttribute(name = "vo") BoardListSearchVO vo) {

		// 조회 유형 판정과 실행시킬 메소드를 BoardDaoImpl에서 결정하도록 변경
		// 조회 유형에 따른 조회 결과의 총 갯수를 반환
		int count = boardDao.count(vo);
		// 반환한 조회 결과의 총 갯수(count)를 vo의 count 필드의 값으로 설정
		vo.setCount(count);

		// model에 조회 유형에 따른 조회 결과를 첨부
		model.addAttribute("list", boardDao.selectList(vo));
		
		// 게시글 목록(list.jsp)로 연결
		return "board/list";
	}
}

 

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix = "fmt" uri = "http://java.sun.com/jsp/jstl/fmt" %>

<%-- 오늘 날짜를 구해서 today라는 변수로 만든다 --%>
<jsp:useBean id = "now" class = "java.util.Date"></jsp:useBean>
<c:set var = "today">
	<fmt:formatDate value = "${now}" pattern = "yyyy-MM-dd"/>
</c:set>

<%-- 테스트용 데이터 출력 --%>
<h3>${vo}</h3>

<jsp:include page = "/WEB-INF/views/template/header.jsp">
	<jsp:param value = "게시글 목록" name = "title"/>
</jsp:include>

<div align = "center">
<h1>게시글 목록</h1>

<table border = "1" width = "900">
<thead>
	<%-- 로그인 상태에서만 글쓰기 항목이 보이도록 설정 --%>
	<c:if test = "${loginId != null}">
	<tr>
		<td align = "right" colspan = "5">
			<a href = "write">글쓰기</a>
		</td>
	</tr>
	</c:if>
	
	<tr>
		<th>번호</th>
		<th width = "45%">제목</th>
		<th>작성자</th>
		<th>작성일</th>
		<th>조회수</th>
	</tr>
</thead>

<tbody align = "center">
	<c:forEach var = "boardDto" items = "${list}">
	<tr>
		<td>${boardDto.boardNo}</td>
		<td align = "left">
			<%-- 차수만큼 띄어쓰기 반복 --%>
			<c:forEach var="i" begin="1" end="${boardDto.boardDepth}" step="1">
				&nbsp;&nbsp;
			</c:forEach>
		
			<%-- 말머리가 있을 경우에만 출력 --%>
			<c:if test = "${boardDto.boardHead != null}">
				[${boardDto.boardHead}]
			</c:if>
			
			<%-- 제목을 누르면 해당 게시글의 상세 페이지로 이동하도록 --%>
			<a href = "detail?boardNo=${boardDto.boardNo}">
				${boardDto.boardTitle}
			</a>
			
		</td>
		<td>${boardDto.boardWriter}</td>
		<td>
			<%-- 작성 날짜를 current라는 변수로 만든다 --%>
			<c:set var = "current">
				<fmt:formatDate value = "${boardDto.boardWritetime}" pattern = "yyyy-MM-dd"/>
			</c:set>
			
			<c:choose>
				<%-- today(오늘 날짜)와 current(작성 날짜)가 같다면 시간과 분만 표시--%>
				<c:when test = "${today == current}">	
					<fmt:formatDate value = "${boardDto.boardWritetime}" pattern = "HH:mm"/>
				</c:when>
				<%-- 그렇지 않다면 년도-월-일로 표시 --%>
				<c:otherwise>
					<fmt:formatDate value = "${boardDto.boardWritetime}" pattern = "yyyy-MM-dd"/>
				</c:otherwise>
			</c:choose>
		</td>
		<td>${boardDto.boardRead}</td>
	</tr>
	</c:forEach>
</tbody>

<c:if test = "${loginId != null}">
<tfoot>
	<tr>
		<td align = "right" colspan = "5">
			<a href = "write">글쓰기</a>
		</td>
	</tr>
</tfoot>
</c:if>

</table>

<%-- 페이지 네비게이터(변경 전) --%>
<h4> &laquo; &lt; 1 2 3 4 5 6 7 8 9 10 &gt; &raquo; </h4>

<%-- 페이지 네비게이터(변경 후) --%>
<h4>
<%-- 맨 처음 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isFirst()}"> <%-- 맨 처음 페이지가 아니라면 --%>
		<a href = "list?p=${vo.firstBlock()}&${vo.parameter()}">&laquo;</a> <%-- 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&laquo;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 이전 구간의 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasPrev()}"> <%-- 이전 페이지가 있다면 --%>
		<a href = "list?p=${vo.prevBlock()}&${vo.parameter()}">&lt;</a> <%-- 이전 구간의 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&lt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 현재 구간의 페이지 이동 --%>
<%-- 변수명을 i로 하며 시작과 끝은 vo의 startBlock(), endBlock()의 반환값으로, 간격은 1로 한다  --%>
<c:forEach var = "i" begin = "${vo.startBlock()}" end = "${vo.endBlock()}" step = "1">
	<a href = "list?p=${i}&${vo.parameter()}">${i}</a>
</c:forEach>

<%-- 다음 구간의 첫 번째 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasNext()}"> <%-- 다음 페이지가 있다면 --%>
		<a href = "list?p=${vo.nextBlock()}&${vo.parameter()}">&gt;</a> <%-- 다음 구간의 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&gt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 맨 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isLast()}"> <%-- 맨 마지막 페이지가 아니라면 --%>
		<a href = "list?p=${vo.lastBlock()}&${vo.parameter()}">&raquo;</a> <%-- 맨 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise>
		<a href = "">&raquo;</a>
	</c:otherwise>
</c:choose>

</h4>

<%-- 검색창 --%>
<form action = "list" method = "get">

<input type = "hidden" name = "size" value = "${vo.size}">

<select name = "type" required>
	<option value = "board_title" <c:if test = "${vo.type == 'board_title'}">selected</c:if>>제목</option>
	<option value = "board_content" <c:if test = "${vo.type == 'board_content'}">selected</c:if>>내용</option>
	<option value = "board_writer" <c:if test = "${vo.type == 'board_writer'}">selected</c:if>>작성자</option>
</select>

<input type = "search" name = "keyword" placeholder = "검색어" required value = "${vo.keyword}">

<button type = "submit">검색</button>
</form>
</div>

<jsp:include page = "/WEB-INF/views/template/footer.jsp"></jsp:include>

 

- 페이지 이동을 할 수 있도록 페이지 네비게이터의 각 번호에 하이퍼링크 부여

 

<%-- 페이지 네비게이터 --%>
<h4>
<%-- 맨 처음 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isFirst()}"> <%-- 맨 처음 페이지가 아니라면 --%>
		<a href = "list?p=${vo.firstBlock()}">&laquo;</a> <%-- 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&laquo;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 이전 구간의 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasPrev()}"> <%-- 이전 페이지가 있다면 --%>
		<a href = "list?p=${vo.prevBlock()}">&lt;</a> <%-- 이전 구간의 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&lt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 현재 구간의 페이지 이동 --%>
<%-- 변수명을 i로 하며 시작과 끝은 vo의 startBlock(), endBlock()의 반환값으로, 간격은 1로 한다  --%>
<c:forEach var = "i" begin = "${vo.startBlock()}" end = "${vo.endBlock()}" step = "1">
	<a href = "list?p=${i}">${i}</a>
</c:forEach>

<%-- 다음 구간의 첫 번째 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasNext()}"> <%-- 다음 페이지가 있다면 --%>
		<a href = "list?p=${vo.nextBlock()}">&gt;</a> <%-- 다음 구간의 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&gt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 맨 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isLast()}"> <%-- 맨 마지막 페이지가 아니라면 --%>
		<a href = "list?p=${vo.lastBlock()}">&raquo;</a> <%-- 맨 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise>
		<a href = "">&raquo;</a>
	</c:otherwise>
</c:choose>

</h4>

 

- vo는 총 게시글의 수(count)의 설정이 끝난 BoardListSearchVO vo 이다

 

- 현재 구간의 페이지 이동

<%-- 현재 구간의 페이지 이동 --%>
<%-- 변수명을 i로 하며 시작과 끝은 vo의 startBlock(), endBlock()의 반환값으로, 간격은 1로 한다  --%>
<c:forEach var = "i" begin = "${vo.startBlock()}" end = "${vo.endBlock()}" step = "1">
	<a href = "list?p=${i}">${i}</a>
</c:forEach>
int startBlock() 현재 페이지에 표시할 블럭의 시작 번호 반환
int endBlock() 현재 페이지에 표시할 블럭의 끝 번호 반환

 

- 이전 구간의 마지막 페이지로 이동 & 다음 구간의 첫 번째 페이지로 이동

<%-- 이전 구간의 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasPrev()}"> <%-- 이전 페이지가 있다면 --%>
		<a href = "list?p=${vo.prevBlock()}">&lt;</a> <%-- 이전 구간의 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&lt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 다음 구간의 첫 번째 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasNext()}"> <%-- 다음 페이지가 있다면 --%>
		<a href = "list?p=${vo.nextBlock()}">&gt;</a> <%-- 다음 구간의 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&gt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>
boolean hasPrev() 이전 블럭 구간이 존재하는지의 여부 반환
int prevBlock() 이전 블록의 번호 반환
boolean hasNext() 다음 블럭 구간이 존재하는지의 여부 반환
int nextBlock() 다음 블록의 번호 반환

 

- 맨 처음 페이지로 이동 & 맨 마지막 페이지로 이동

<%-- 맨 처음 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isFirst()}"> <%-- 맨 처음 페이지가 아니라면 --%>
		<a href = "list?p=${vo.firstBlock()}">&laquo;</a> <%-- 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&laquo;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 맨 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isLast()}"> <%-- 맨 마지막 페이지가 아니라면 --%>
		<a href = "list?p=${vo.lastBlock()}">&raquo;</a> <%-- 맨 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise>
		<a href = "">&raquo;</a>
	</c:otherwise>
</c:choose>
boolean isFirst() 현재 블럭이 시작 번호 블럭인지(첫 페이지인지)의 여부 반환
int firstBlock() 블럭의 시작 번호 반환(첫 페이지 번호)
boolean isLast() 현재 블럭이 끝 번호 블럭인지(마지막 페이지인지)의 여부 반환
int lastBlock() 블럭의 끝 번호 반환(마지막 페이지 번호 = 마지막 게시글이 속한 블럭 번호)

 

- 검색 조회시에도 페이징을 유지할 수 있도록 하이퍼링크에 parameter() 추가

<%-- 페이지 네비게이터(변경 후) --%>
<h4>
<%-- 맨 처음 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isFirst()}"> <%-- 맨 처음 페이지가 아니라면 --%>
		<a href = "list?p=${vo.firstBlock()}&${vo.parameter()}">&laquo;</a> <%-- 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&laquo;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 이전 구간의 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasPrev()}"> <%-- 이전 페이지가 있다면 --%>
		<a href = "list?p=${vo.prevBlock()}&${vo.parameter()}">&lt;</a> <%-- 이전 구간의 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&lt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 현재 구간의 페이지 이동 --%>
<%-- 변수명을 i로 하며 시작과 끝은 vo의 startBlock(), endBlock()의 반환값으로, 간격은 1로 한다  --%>
<c:forEach var = "i" begin = "${vo.startBlock()}" end = "${vo.endBlock()}" step = "1">
	<a href = "list?p=${i}">${i}</a>
</c:forEach>

<%-- 다음 구간의 첫 번째 페이지로 이동 --%>
<c:choose>
	<c:when test = "${vo.hasNext()}"> <%-- 다음 페이지가 있다면 --%>
		<a href = "list?p=${vo.nextBlock()}&${vo.parameter()}">&gt;</a> <%-- 다음 구간의 첫 번째 페이지로 이동 --%>
	</c:when>
	<c:otherwise> <%-- 그렇지 않다면 --%>
		<a href = "">&gt;</a> <%-- 아무런 페이지 변화가 없도록 --%>
	</c:otherwise>
</c:choose>

<%-- 맨 마지막 페이지로 이동 --%>
<c:choose>
	<c:when test = "${not vo.isLast()}"> <%-- 맨 마지막 페이지가 아니라면 --%>
		<a href = "list?p=${vo.lastBlock()}&${vo.parameter()}">&raquo;</a> <%-- 맨 마지막 페이지로 이동 --%>
	</c:when>
	<c:otherwise>
		<a href = "">&raquo;</a>
	</c:otherwise>
</c:choose>

</h4>

 

- 검색시 크기 유지

<%-- 검색창 --%>
<form action = "list" method = "get">

<input type = "hidden" name = "size" value = "${vo.size}">

<select name = "type" required>
	<option value = "board_title" <c:if test = "${vo.type == 'board_title'}">selected</c:if>>제목</option>
	<option value = "board_content" <c:if test = "${vo.type == 'board_content'}">selected</c:if>>내용</option>
	<option value = "board_writer" <c:if test = "${vo.type == 'board_writer'}">selected</c:if>>작성자</option>
</select>

<input type = "search" name = "keyword" placeholder = "검색어" required value = "${vo.keyword}">

<button type = "submit">검색</button>
</form>