JAVA 웹개발 과정 국비 60일차 정리

60일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


59일차에는 시험을 봤다.
Selenium이 하나도 기억나지 않아서 애먹었다..

1. 게시판 만들기 이어서..

1) 게시글 수정 기능 구현

  • 작성자와 loginID가 같을 때 수정하기 버튼 생성
  • 수정하기 버튼 클릭시 페이지 전환 없이 readonly 속성 삭제로 기능 구현
  • 수정시 삭제하기 버튼 삭제 및 수정완료, 취소 버튼 구현
    • 버튼을 미리 생성 후 display:none을 했다가 나중에 나타내기
    • 수정 하기 전 내용을 백업, 취소를 누르면 돌아가게 만든다.
    • 수정완료 버튼을 누르면 modify.board로 이동
    • modify.board에서 dao에게 modify 시킨 후 seq를 가지고 게시글로 되돌아가기
    • seqsumbit할때 hidden으로 숨겨서 가져간다.

2) 임시 데이터 대량 생산

  • 페이징 기능 구현을 위해 임시로 데이터를 대량 생산하는 방법
  • for문을 돌려서 DB에 게시글을 많이 저장해준다.
  • PreparedStatement로는 for문을 돌리기 까다롭기 때문에 Statement로 실행
  • 한번 실행하고 지워주자.

3) Page Navigator 구현

사실 Navigator를 구현하는 작업은 DAO도, 컨트롤러에도 어울리지 않는다.
컨트롤러는 Web tier라고 부르는데, 웹과 관련된 작업과 제어 역할만 하기 때문이다. (request에서 값을 꺼내고, response에 값을 넣고.. 등등) DAO는 데이터를 통제하는 Model 객체이다. (DB와 관련된 작업, 크롤링 작업 등 데이터를 가져오는 객체)
그렇기 때문에 페이지를 만드는 이 코드는 두 곳 모두 있어서는 안된다.
정확히 MVS를 만들 때는 컨트롤러와 DAO 사이에 Service 레이어가 존재하고, 이러한 비즈니스 로직 관련된 작업은 여기에 들어가게 된다.
그러나 지금은 아직 그 단계가 아니고 Service 레이어를 빼고 가기 때문에 부득이하게 지금 방법이 최선이다.

html태그를 붙이는 작업을 List에 담아서 jsp쪽에서 받아서 만들어줘도 되지 않느냐? 라고 할 수 도 있지만, 클라이언트가 보는 view 페이지도 결국 서버쪽에서 렌더링하기 때문에 서버에 부하가 걸리는 것은 마찬가지이다.
그래서 Navi만 덩그러니 클라이언트에 보내는 기법이 있는데 이것이 바로 내일 배울 ajax이다!!
결론은 어쨌든 지금은 이런 방법으로 해야한다는 것 ㅋㅋㅋ

① DAO에 getPageNavi 메소드 생성

ⓐ 정보 수집
ㄱ. 총 몇 개의 레코드(게시글)을 가지고 있는지?

DB에서 전체 레코드 갯수를 가져오는 메소드 getRecordCount를 별도로 구현한다.
DAO에서만 사용할 것이므로 private로 만든다.

private int getRecordCount() throws Exception {
	String sql = "SELECT COUNT(*) FROM board";
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);
			ResultSet rs = pstat.executeQuery();){
		rs.next();
		return rs.getInt(1);
	}
}

recordTotalCount 변수에 담아준다.

int recordTotalCount = this.getRecordCount();
ㄴ. 한 페이지에 몇 개의 게시글을 보여줄 것인지?

기본값으로 10을 많이 준다.

int recordCountPerPage = 10;
ㄷ. 한 페이지에 네비게이터는 몇 개를 보여줄 것인지?
int naviCountPerPage = 10;
ㄹ. 총 몇 개의 페이지를 가지는지?

ㄱ,ㄴ의 정보들을 알기 때문에, 총 몇개 페이지를 가지는지 계산할 수 있다.
예를 들면, 143개의 레코드가 있고, 한 페이지에 10개씩이라고 하면 총 15페이지가 나올 것이다.

n개의 레코드, 페이지당 10개씩 출력한다면 총 몇개의 페이지가 존재할까?  
n을 10으로 나눴을 때 값이 a일 때, n이 나누어 떨어진다면 a개의 페이지가 존재한다.  
n을 10으로 나눴을 때 나누어 떨어지지 않는다면 a+1개의 페이지가 존재한다.

위의 규칙을 코드로 풀어보면 아래와 같이 나타낼 수 있다.

int pageTotalCount = 0;
if(recordTotalCount % recordCountPerPage == 0) {
	pageTotalCount = recordTotalCount / recordCountPerPage;
}else {
	pageTotalCount = recordTotalCount / recordCountPerPage + 1;
}	
ㅁ. 내가 어느 페이지에 있는지?

내가 어느 페이지에 있는지는 클라이언트가 어떤 페이지를 누르느냐에 따라 정해진다. 즉, 클라이언트로부터 받아와야 하는 값이다.
그러므로 getPageNavi 메소드에 파라미터로 currentPage를 받는다.

ㅂ. 현재 페이지를 기준으로 시작과 끝의 네비 번호는?
현재 페이지가 3일 때, 네비는 1~ 10을 보여준다.
현재 페이지가 11일 때, 네비는 11~20을 보여준다.
현재 페이지가 27일 때, 네비는 21~30을 보여준다. 
위 예들로 앞자리 수가 같다라는 규칙성을 찾을 수 있다.

자바에서는 int값끼리의 나눗셈은 int값이 나오기 때문에, 나머지를 버림처리 할 수 있다.
예를들면 현재 페이지가 27일 때, 27/10은 2이고, 10을 곱하면 20이 된다. 여기에 1을 더해주면 시작 네비 값을 구할 수 있다.

이것을 이용하여 코드를 아래와 같이 작성한다.

int startNavi = (currentPage-1)/naviCountPerPage * naviCountPerPage +1;
int endNavi = startNavi+(naviCountPerPage-1);

currentPage-1인 이유는 현재 페이지가 10의 배수일 때, 예를 들면 현재 페이지가 20일 때 위의 식에 -1을 빼고 대입하면 20/10으로 2, 10을 곱하면 20, +1을 하면 21으로 현재 페이지와 시작 페이지가 맞지 않는 경우가 생긴다.
그러나 현재 페이지에 -1을 해주면 현재 페이지가 19일 때는 18/20으로 1, 10을 곱하면 10, +1을하면 11으로 처음 네비 값이 잘 나타나고, 현재 페이지가 20일 때는 19/20으로 1, 10을 곱하면 10, +1을 하면 11로 값이 잘 나타난다. 또한 마지막으로 현재 페이지가 21일 때를 보면, 20/10으로 2, 10을 곱하면 20, +1을 하면 21으로 마찬가지로 공식이 잘 동작하는 것을 볼 수 있다.

이때, 마지막 네비의 값이 전체 페이지의 수를 넘어가면 안된다.
예를 들어 전체 페이지가 15까지인데, 현재 14페이지를 보고있다고 네비의 값이 11~20이 나오면 안되기 때문에 이 경우에는 마지막 네비의 값은 전체 페이지의 수로 맞춰준다.

if(endNavi > pageTotalCount) {
	endNavi = pageTotalCount;
}	
ㅅ. 이전으로가기, 이후로가기 화살표가 필요한지?

화살표가 필요 없는 경우를 생각해보자.
시작 네비 값이 1일 경우에는 이전 네비가 없기 때문에 필요 없다.
마찬가지로 마지막 네비 값이 총 페이지 개수와 같다면 이후 네비가 없기 때문에 필요 없다.
이 두 경우에는 boolean값을 false로 만들어준다.

boolean needPrev = true;
boolean needNext = true;

if(startNavi==1) {needPrev=false;}
if(endNavi==pageTotalCount) {needNext=false;}
ⓑ 보안 작업

currentPage 값을 클라이언트가 조작할 가능성이 있기 때문에 또는 어떤 예상하지 못한 상황이 생길 수 있기 때문에 currentPage이 유효한 값이 아니라면 막아주는 보안 작업을 해준다.

if(currentPage < 1) {
	currentPage = 1;
}else if(currentPage > pageTotalCount) {
	currentPage = pageTotalCount;
}
ⓒ Navi 만들기

Navi의 개념은 String값에 각 페이지의 주소를 a태그로 붙여서 화면에 출력해주는 것이다.

ㄱ. < 만들기
만약 < 가 필요하다면?
< 를 누르면 startNavi-1 페이지로 넘어간다.
예를 들면 현재 페이지가 14일 때, startNavi는 11이고 endNavi는 20이다.
< 를 누르면 13페이지로 이동되며, 네비는 11~20을 보여준다.
if(needPrev) {pageNavi += "<a href='/toBoard.board?cpage="+(startNavi-1)+"'><</a>"+" ";}
ㄴ. 네비 숫자 출력하기

startNavi부터 endNavi 까지 for문을 돌려주며 빈 문자열에 값을 더해준다.

String pageNavi = "";
for (int i=startNavi;i<=endNavi;i++) {
	pageNavi += "<a href='/toBoard.board?cpage='+"i"+>"+i+"</a>"+" ";
}
ㄷ. > 만들기

ㄱ과 동일한 방법으로 코드를 작성한다.

if(needNext) {pageNavi += "<a href='/toBoard.board?cpage="+(endNavi+1)+"'>></a>";}
ⓓ getPageNavi 메소드 소스코드
public String getPageNavi(int currentPage) throws Exception{

	int recordTotalCount = this.getRecordCount();
	int recordCountPerPage = 10;
	int naviCountPerPage = 10;
	int pageTotalCount = 0;
	if(recordTotalCount % recordCountPerPage == 0) {
		pageTotalCount = recordTotalCount / recordCountPerPage;
	}else {
		pageTotalCount = recordTotalCount / recordCountPerPage + 1;
	}

	if(currentPage < 1) {
		currentPage = 1;
	}else if(currentPage > pageTotalCount){
		currentPage = pageTotalCount;
	} 

	int startNavi = (currentPage-1)/naviCountPerPage * naviCountPerPage +1;
	int endNavi = startNavi+(naviCountPerPage-1);

	if(endNavi > pageTotalCount) {
		endNavi = pageTotalCount;
	}

	boolean needPrev = true;
	boolean needNext = true;

	if(startNavi ==1) {
		needPrev = false;
	}

	if(endNavi == pageTotalCount) {needNext = false;}

	String pageNavi ="";
	if(needPrev) {pageNavi += "<a href='/toBoard.board?cpage="+(startNavi-1)+"'><</a>"+" ";}
	for (int i=startNavi;i<=endNavi;i++) {
		pageNavi += "<a href='/toBoard.board?cpage="+i+"'>"+ i +"</a>"+" ";
	}
	if(needNext) {pageNavi += "<a href='/toBoard.board?cpage="+(endNavi+1)+"'>></a>";}
	return pageNavi; 
}

② Navi 화면에 나타내기

getPageNavi 메소드로 String 값인 Navi를 생성할 수 있다.
그러나 이 메소드를 사용하려면 현재 page 값을 알아야 한다.
게시판으로 이동하는 여러 경우의 수를 따져보면,

  1. 처음 게시판으로 이동할 때
  2. 네비의 각 페이지를 눌렀을 때

두 가지 경우가 존재한다.
먼저 2번의 경우에서 toBoard.board로 cpage값을 가지고 이동하게끔 a태그에 주소값을 적어주었기 때문에 컨트롤러에서 이 값을 받게만들면 된다.
그리고 받은 현재 페이지 값을 파라미터로 받는 getPageNavi로 Navi 값을 얻어와서 list.jspsetAttribute로 보내준다.

int currentPage = Integer.parseInt(request.getParameter("cpage"));
String navi = dao.getPageNavi(currentPage);
request.setAttribute("navi", navi);

그러나 1번의 경우에는 현재 페이지 값을 넘겨주지 않았으므로 index.jsp에서toBoard를 누를 때 이동하는 주소값에 ?cpage=1를 붙여준다.

$("#toBoard").on("click",function(){
	location.href="/toBoard.board?cpage=1";
}

마지막으로 list.jsp로 가서 ${navi}를 출력해준다.

③ 페이징 처리되는 글 범위 출력

이제 Navi 페이지에 맞춰서 게시판 내용을 잘라서 출력해준다.
현재는 dao.selectAll 메소드로 모든 게시물 내용을 출력해주기 때문에, 게시글을 시작번호부터 끝번호까지 보여주게 하는 selectByBound 메소드를 따로 만든다.

selectByBound 메소드 생성

기본적으로 SelectAll과 비슷하지만, 쿼리문에 ROW_NUMBER() OVER을 사용하여 seq가 내림차순으로 되어있을 때 원하는 갯수만큼 끊어서 SELECT 해 올 수 있게 한다.
ROW_NUMBER() OVER() 참고

public List<BoardDTO> selectByBound(int start, int end) throws Exception{
	String sql = "SELECT * FROM (SELECT board.*, row_number() OVER(ORDER BY seq DESC) rn FROM board) WHERE rn BETWEEN ? AND ?";
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);
			){
		pstat.setInt(1, start);
		pstat.setInt(2, end);
		try(ResultSet rs = pstat.executeQuery();){
			List<BoardDTO> list = new ArrayList<>();
			while(rs.next()) {
				int seq = rs.getInt("seq");
				String writer = rs.getString("writer");
				String title= rs.getString("title");
				String contents = rs.getString("contents");
				Timestamp write_date = rs.getTimestamp("write_date");
				int view_count = rs.getInt("view_count");
				BoardDTO dto= new BoardDTO(seq, writer, title, contents, write_date, view_count);
				list.add(dto);
			}
			return list;
		}

	}
}
ⓑ start, end 변수 만들기

컨트롤러에서 selectAll 메소드 대신 selectByBound 메소드로 바꿔준다.
그리고 시작 숫자와 끝나는 숫자 변수를 생성한다.

현재 1페이지에 있다면? ROWNUM이 1~10인 게시글을 보여줘야함
현재 2페이지에 있다면? ROWNUM이 11~20인 게시글을 보여줘야함
현재 3페이지에 있다면? ROWNUM이 21~30인 게시글을 보여줘야함

위의 예로 규칙성을 찾아보면 아래와 같이 두 변수를 만들 수 있다.
10은 dao안에 있는 recordCountPerPage 변수이고, 나중에는 정리해서 바깥으로 꺼내줄 것이므로 지금은 그냥 10으로 두자.
마지막으로 list.jsp로 값을 전달해준다.

int start = currentPage * 10 - 9;
int end = currentPage * 10;
List<BoardDTO> list = dao.selectByBound(start, end);
request.setAttribute("list", list);
request.getRequestDispatcher("/board/list.jsp").forward(request, response);

2. 소스 코드

Board

댓글남기기