본문 바로가기
Dev/Java

(JSP2.1) 답변형 게시판 구현 - 목록 구현하기

by vellahw 2022. 9. 28.

 

 

게시글 목록 관련 구성 요소

 

 

1. ArticleListModel 클래스 구현 (게시글 목록 화면 구성에 필요한 정보 제공)

package board2;

//게시글 목록 화면 구성에 필요한 정보를 저장

import java.util.ArrayList;
import java.util.List;

public class ArticleListModel {
	
	private List<Article> articleList; //게시판에 보여줄 데이터 리스트
	private int requestPage; //요청 페이지
	private int totalPageCount; //전체 페이지 수
	private int startRow; //시작 행
	private int endRow; //마지막 행
	
	//생성자1
	public ArticleListModel() {
		this(new ArrayList<Article>(), 0, 0, 0, 0); //this(): 같은 클래스의 다른 생성자 키워드 호출
	//= ArticleListModel(List<Article> articleList, int requestPageNumber, int totalPageCount, int startRow, int endRow
	}
	
	//생성자2(set 대신 만듦)
	public ArticleListModel(List<Article> articleList, int requestPageNumber, 
				int totalPageCount, int startRow, int endRow) {
		this.articleList = articleList;
		this.requestPage = requestPageNumber;
		this.totalPageCount = totalPageCount;
		this.startRow = startRow;
		this.endRow = endRow;
	}
	
	//위에 선언한 private 변수를 가져다 쓰기 위해 get 정의
	public List<Article> getArticleList() {
		return articleList;
	}
	
	public boolean isHasArticle() {
		return ! articleList.isEmpty(); //리스트에 객체가 없으면 false 리턴
	}
	
	public int getRequestPage() {
		return requestPage;
	}
	
	public int getTotalPageCount() {
		return totalPageCount;
	}

	public int getStartRow() {
		return startRow;
	}

	public int getEndRow() {
		return endRow;
	}
}

 

 

2. ArticleDao 클래스의 목록 관련 메소드 구현

package board2;

//ListArticleService 클래스가 요청한 페이지에 해당하는 게시글 목록을 읽어오기 위해서 사용하는 클래스

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import loader.JdbcUtil;

public class ArticleDao {
	private static ArticleDao instance = new ArticleDao();
	
	public static ArticleDao getInstance() {
		return instance;
	}
	
	private ArticleDao() {}

	//게시글의 전체 개수 구함
	public int selectCount(Connection conn) throws SQLException {
		Statement stmt = null;
		ResultSet rs = null;
		
		try {
			stmt = conn.createStatement();
			rs = stmt.executeQuery("select count(*) from article");
			rs.next();
			return rs.getInt(1);
			
		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(stmt);
		}
	}

	//전체 게시글 중에서 시작 행과 마지막 행에 속하는 게시글을 읽어옴
	public List<Article> select(Connection conn, int firstRow, int endRow) throws SQLException {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = conn.prepareStatement(" select article_id, group_id, sequence_no, posting_date, read_count, writer_name, password, title from ( "
										+ " select rownum rnum, article_id, group_id, sequence_no, posting_date, read_count, writer_name, password, title from ( "
										+ " select * from article m order by m.sequence_no desc "
										+ " ) where rownum <= ? "
										+ " ) where rnum >= ?" );
			
			pstmt.setInt(1, endRow);
			pstmt.setInt(2, firstRow);
			rs = pstmt.executeQuery();
			
			//데이터가 존재하지 않을 경우
			if(!rs.next()) { 
				return Collections.emptyList(); //빈 List 리턴
			}
			
			//데이터가 존재할 경우
			List<Article> articleList = new ArrayList<Article>();
			do {
         		   //게시글 목록을 읽어올 땐 게시글의 '내용'이 필요하지 않기 때문에 false 지정
				Article article = makeArticleFromResultSet(rs, false);
				articleList.add(article); //articleList에 article 객체를 저장
			} while (rs.next());
			
			return articleList;
			
		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(pstmt);
		}
	}
 	
	//ResultSet으로부터 데이터를 읽어와 Article 객체를 생성
	private Article makeArticleFromResultSet(ResultSet rs, boolean readCotent) throws SQLException {
	//boolean readContent의 값 여부에 따라 content 칼럼의 값을 읽어올지 여부 결정
		
		Article article = new Article(); 
		article.setId(rs.getInt("article_id"));
		article.setGroupId(rs.getInt("group_id"));
		article.setSequenceNumber(rs.getString("sequence_no"));
		article.setPostingDate(rs.getTimestamp("posting_date"));
		article.setReadCount(rs.getInt("read_count"));
		article.setWriterName(rs.getString("writer_name"));
		article.setPassword(rs.getString("password"));
		article.setTitle(rs.getString("title"));
		
		//true라면 content 칼럼의 값 읽어옴
		if(readCotent) {
			article.setContent(rs.getString("content"));
		}
		return article;
	}

*코드 속 쿼리문의 작동 순서

1. select * from article m order by m.sequence_no desc

   => article 테이블 m에서 테이블의 모든 컬럼의 데이터를 순서 번호를 기준으로 내림차순 정렬한다.

2. select rownum rnum, article_id, group_id, sequence_no, posting_date, read_count, writer_name, password, title

   from ( select * from article m order by m.sequence_no desc ) where rownum <= ?

  => article 테이블 m에서 테이블의 모든 컬럼을 순서 번호를 기준으로 내림차순 정렬한 데이터로부터 글번호, 그룹번호, 순서 번호, 글작성일, 조회수, 작성자, 비밀번호, 제목을 추출하는데 rownum rnum으로 순번을 매겨 정렬하여 rownum이 ?(끝 행)보다 작거나 같은 데이터만 가져온다.

3. select article_id, group_id, sequence_no, posting_date, read_count, writer_name, password, title

   from ( select rownum rnum, article_id, group_id, sequence_no, posting_date, read_count, writer_name, password, title

   from ( select * from article m order by m.sequence_no desc ) where rownum <= ? ) where rnum >= ?

  => 위에서 추출한 데이터로부터  글번호, 그룹번호, 순서 번호, 글작성일, 조회수, 작성자, 비밀번호, 제목을 추출하는데 rnum이 ?(시작 행)보다 크거나 같은 데이터만 가져온다. 

 

 

3. ListArticleService 클래스 구현

 : ArticleDao 클래스의 selectCount() 메소드와 select() 메소드를 이용해 사용자가 요청한 페이지에 해당하는 게시글 목록을 구한다.

package board2;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import book.ConnectionProvider;
import loader.JdbcUtil;

//게시글 목록 구하기

public class ListArticleService {
	private static ListArticleService instance = new ListArticleService(); 

	public static ListArticleService getInstance() {
		return instance;
	}
	
	//상수 선언: 한 페이지당 보여질 게시글 수=10개
	public static final int COUNT_PER_PAGE = 10;
						//요청 페이지 번호<ㄱ
	public ArticleListModel getArticleList(int requestPageNumber) {
		if(requestPageNumber < 0) { //요청 페이지 번호가 없다면
			throw new IllegalArgumentException("page number < 0 : "
								+ requestPageNumber );
		} //예외 발생
		
		ArticleDao articleDao = ArticleDao.getInstance();
		Connection conn = null;
		
		try {
			conn = ConnectionProvider.getConnection(); //커넥션 객체 생성
			//articleDao로부터 게시글의 전체 개수를 구해 totalArticleCount에 저장
			int totalArticleCount = articleDao.selectCount(conn); 
			
			//게시글이 없다면 빈 리스트 리턴 
			if(totalArticleCount == 0) {
				return new ArticleListModel();
			}
			
			//게시글이 있다면 전체 게시글 개수를 이용해 전체 페이지 수 구함
			int totalPageCount = calculateTotalPageCount(totalArticleCount);
			
			//시작행 = (요청 페이지 번호-1)*한 페이지 당 보여지는 게시글 개수 + 1
			int firstRow = (requestPageNumber - 1) * COUNT_PER_PAGE + 1; 
			//끝행 = 시작행 + 한 페이지 당 보여지는 게시글 개수 - 1;
			int endRow = firstRow + COUNT_PER_PAGE - 1;	
			
			//끝행이 게시글 전체 개수보다 크다면 끝행=게시글 전체 개수
			if(endRow > totalArticleCount) {
				endRow = totalArticleCount;
			}
			
			//게시글 목록 구함
			//articleDao의 select 메소드에 시작행과 끝행을 담아 articleList 
			List<Article> articleList = articleDao.select(conn, firstRow, endRow);
			
			//ArticleListModel 객체 생성 - articleList, 요청 페이지 번호, 전체페이지개수, 시작행, 끝행 담음
			ArticleListModel articleListView = new ArticleListModel(
					articleList, requestPageNumber, totalPageCount, firstRow, endRow);
	
			return articleListView;

		} catch (SQLException e) {
			throw new RuntimeException("DB 에러 발생: " + e.getMessage(), e);
		
		} finally {
			JdbcUtil.close(conn);
		}
	}
	
	//전체 게시글 개수로부터 전체 페이지 개수를 구해주는 메소드 
	public int calculateTotalPageCount(int totalArticleCount) {
		//전채 게시글 개수가 없다면 0 리턴
		if(totalArticleCount == 0) {
			return 0;
		}
		
		//페이지 개수 = 전체 게시글 개수 / 한 페이지 당 보여지는 게시글 개수 (게시글 20개 / 10 = 2 페이지)
		int pageCount = totalArticleCount / COUNT_PER_PAGE;
		
		//전체 게시글 개수를 한 페이지 당 보여지는 게시글 개수로 나눈 나머지 값이 0보다 크다면
		if(totalArticleCount % COUNT_PER_PAGE > 0) {
			pageCount++; //페이지 개수를 증가
		}
		return pageCount;
	}

 

증감 연산자 부분

게시글 총 개수 totalArticleCount를 21으로 가정

int pageCount = 21 / 10  ==> 2

if(21%10 > 0) {

2++ }

return 3;

==> 3페이지가 됨

 

 

4. JSP 파일 작성

 1) list.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ page import="board2.ArticleListModel" %>    
<%@ page import="board2.ListArticleService" %>      
<%
	String pageNumberString = request.getParameter("p"); //파라미터로 넘어온 p를 String 타입 페이지 번호로 받음
	int pageNumber = 1;
	
	//데이터가 있다면
	if(pageNumberString != null && pageNumberString.length() > 0) {
		pageNumber = Integer.parseInt(pageNumberString); //String 타입 페이지 번호를 parseInt로 받아준다
	}
	
	//ListArticleService 객체 생성
	ListArticleService listService = ListArticleService.getInstance();
	//ListArticleService 클래스의 getArticleList 메소드에 pageNumber 받아 articleListModel에 리턴
	ArticleListModel articleListModel = listService.getArticleList(pageNumber);
	//request 영역에 listModel이란 이름으로 articleListModel 저장
	request.setAttribute("listModel", articleListModel); 
	
	//articleListModel의 전체 페이지 개수가 0보다 크다면
	if(articleListModel.getTotalPageCount() > 0) {
		//시작 페이지 번호 = (ArticleListModel에서 받아온 요청페이지번호 -1) / 10 * 10 + 1
		int beginPageNumber = (articleListModel.getRequestPage() - 1) / 10 * 10 + 1;
		//끝 페이지 번호 = 시작 페이지 번호 + 9
		int endPageNumber = beginPageNumber + 9;
		
		//끝 페이지 번호가 전체 페이지 개수보다 크다면
		if(endPageNumber > articleListModel.getTotalPageCount()) {
			//끝 페이지 번호 = 전체 페이지 개수
			endPageNumber = articleListModel.getTotalPageCount();
		}
		
		//request 영역에 시작/끝 페이지 번호 저장
		request.setAttribute("beginPage", beginPageNumber);
		request.setAttribute("endPage", endPageNumber);
		
	}
%>
<jsp:forward page="list_view.jsp" /> <!-- list_view.jsp로 이동 -->

list.jsp는 p 파라미터로 읽어올 페이지 번호를 전달 받은 뒤, ListArtcleService 클래스의 getArticleList() 메소드에 전달한다.

getArticleList() 메소드는 페이지 번호와 관련된 데이터를 보관한 ArticleListModel 객체를 리턴하며,

list.jsp는 getArticleList() 메소드 객체를 list_view.jsp에 전달한다.

 

 2) list_view.jsp : list.jsp로부터 전달 받은 데이터(articleListModel, beginPage, endPage)를 이용해 목록 화면 생성

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
	response.setHeader("Pragma", "No-cache"); //Pragma 헤더의 값을 No-cache로 지정
	response.setHeader("Cache-Control", "no-cache"); 
	response.addHeader("Cache-Control", "no-store"); //Cache-Control 헤더에 no-cache를 값으로 추가
	response.setDateHeader("Expires", 1L); //Expires 헤더 값을 1L로 지정(1970년 1월 1일 이후 흘러간 시간을 1/1000초 단위로 나타냄)
%>

<html>
<head>
<title>게시글 목록</title>
</head>
<body>
<table border="1">
<!-- request.setAttribute("listModel", articleListModel);  -->
<c:if test="${listModel.totalPageCount > 0}" >
	<tr>
		<td colspan="5">
		${listModel.startRow}-${listModel.endRow}
		[${listModel.requestPage}/${listModel.totalPageCount}]
		</td>
	</tr>
</c:if>

	<tr>
		<td>글 번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>작성일</td>
		<td>조회수</td>
	</tr>
	
<c:choose>
	<!-- hasArticle : 리스트에 객체가 없으면 false 리턴  -->
	<c:when test="${listModel.hasArticle == false}">
		<tr>
			<td colspan="5">
			 게시글이 없습니다.
			</td>
		</tr>
	</c:when>
	
	<c:otherwise>
		<!-- 반복 - 리스트에 게시글이 있을 때까지 -->
		<c:forEach var="article" items="${listModel.articleList}">
		<tr>
			<!-- id = 글 번호 -->
			<td>${article.id}</td>
			
			<td>
			<!-- 중첩 레벨 판단 -->
			<!-- 게시글의 레벨이 0보다 크다면 -->
			<c:if test="${article.level > 0}">
				<!-- 1부터 게시글의 레벨일 때까지 '-' 기호 출력 하고 끝에 부등호 '<' (&gt;) 붙임 -->
				<c:forEach begin="1" end="${article.level}">-</c:forEach>&gt;
			</c:if>
			<!-- 글번호와 요청 페이지 정보를 받아오는 주소값 생성 -->
			<c:set var="query"
					value="articleId=${article.id}&p=${listModel.requestPage}" />
			<!-- 생성한 주소 값을 url 태그의 value 속성에 넣어주고 게시글 제목을 누르면 해당 url로 이동 -->
			<a href="<c:url value="read.jsp?${query}"/>">
			${article.title}
			</a>
			</td>
			
			<!-- 작성자, 작성일, 조회수 출력 -->
			<td>${article.writerName}</td>
			<td>${article.postingDate}</td>
			<td>${article.readCount}</td>
		</tr>
	</c:forEach>
	
	<tr>
		<!-- 페이징 -->
		<td colspan="5">
		<!-- 시작 번호가 10보다 크다면 (시작번호-1)한 값을 페이지 번호로 넘겨 url 생성
				후 '이전'을 누르면 해당  url로 이동 -->
		<c:if test="${beginPage>10}">
		<a href="<c:url value="list.jsp?p=${beginPage-1}" />">이전</a>
		</c:if>
		<!-- 반복: 시작 페이지~끝 페이지까지를 대괄호[]안에 넣어 나열해줌+url 누르면 해당 페이지로 이동 -->
		<c:forEach var="pno" begin="${beginPage}" end="${endPage}">
		<a href="<c:url value="list.jsp?p=${pno}" />">[${pno}]</a>
		</c:forEach>
		
		<c:if test="${endPage < listModel.totalPageCount}">
		<a href="<c:url value="list.jsp?p=${endPage + 1}" />">다음</a>
		</c:if>
		</td>
	</tr>
	</c:otherwise>
</c:choose>
	<!-- 글쓰기 버튼 -->
	<tr>
		<td colspan="5">
			<a href="writeForm.jsp">글쓰기</a>
		</td>
	</tr>
</table>
</body>
</html>

 

 

 

 

댓글