안녕하세요 오늘도 코딩공부하러온 jju_developer입니다.
오늘은 학원에서 배운 게시판 만들기에 대해 정리한 글을 공유드립니다.
게시판 기능 목록
🤍구현할 게시판 기능🤍
• 게시글 등록
• 게시글 목록 조회
• 게시글 내용 조회
• 게시글 수정
22.2 예제를 위한 테이블 생성
(1) ddl.sql (게시판 관련 테이블)
[board/sql/ddl.sql]
create table board.article (
article_no int auto_increment primary key,
writer_id varchar(50) not null,
writer_name varchar(50) not null,
title varchar(255) not null,
regdate datetime not null,
moddate datetime not null,
read_cnt int
) engine=InnoDB default character set = utf8;
create table board.article_content (
article_no int primary key,
content text
) engine=InnoDB default character set = utf8;
article 테이블과 article_content을 만들어 줍니다.
둘의 테이블에서 겹치는 부분은 아티클넘버입니다.
그렇기 때문에 식별할 때에는 article_no를 사용해야 합니다.
우선 전체적인 기능을 먼저 보겠습니다!!
로그인 후 article/write.do로 넘어가면 게시글을 작성할 수 있는 곳이 있고
새 글 등록을 누르면
등록이 되었다고 뜹니다.
이렇게 SQL에 검색을 해보면 등록한 게시글이 잘 보입니다.
코드로 볼까요?
여기서 정의해 놓은 /article/write.do 는 article.command.WriteArticleHandler 로 이동을 합니다.
중요한 코드만 보고 넘어가겠습니다.
자바 코드는 아티클의 command 부분, 아티클의 dao, 아티클의 모델, 아티클의 서비스 부분으로 나눠서 패키지를 만들었습니다.
우선 게시글을 만들었을 때에 로직은
로그인 -> 게시글작성(타이틀, 컨탠츠) -> 게시글 목록 확인 -> 게시글 작성으로 돌아가기 (이 부분으로 돌아가면 로그인 화면으로 돌아갑니다.)
그렇게 했을 때 로그인은 이미 DB에 만들어 놓은 회원 정보를 통해서 로그인을 할 수 있습니다.
다시 제대로 아이디를 입력하고 로그인을 하면
이때!!!
주소를 index.jsp가 아니라
http://localhost:8080/chap21/article/write.do
/article/write.do 라고 입력을 했을 때
위의 사진처럼 게시글을 작성할 수 있는 부분이 나오게 됩니다.
어떻게 되었는지 코드로 볼까요?
우선, 주소창에 /article/write.do 라고 입력을 했을 때를 봐야 합니다.
WebContent 밑에 commandHandlerURI.properties를 확인하면
주소창에 /article/write.do를 쳤을 때 어디로 이동을 하는지를 알 수 있습니다.
이 부분이 바로 article.command.WriteArticleHandler 부분입니다.
<WriteArticleHandler.java>
package article.command;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import article.model.Writer;
import article.service.WriteArticleService;
import article.service.WriteRequest;
import auth.service.User;
import mvc.command.CommandHandler;
public class WriteArticleHandler implements CommandHandler {
private static final String FORM_VIEW = "/WEB-INF/view/newArticleForm.jsp";
private WriteArticleService writeService = new WriteArticleService();
@Override
public String process(HttpServletRequest req, HttpServletResponse res) {
if (req.getMethod().equalsIgnoreCase("GET")) {
return processForm(req, res);
} else if (req.getMethod().equalsIgnoreCase("POST")) {
return processSubmit(req, res);
} else {
res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return null;
}
}
private String processForm(HttpServletRequest req, HttpServletResponse res) {
return FORM_VIEW;
}
private String processSubmit(HttpServletRequest req, HttpServletResponse res) {
Map<String, Boolean> errors = new HashMap<>();
req.setAttribute("errors", errors);
User user = (User)req.getSession(false).getAttribute("authUser");
WriteRequest writeReq = createWriteRequest(user, req);
writeReq.validate(errors);
if (!errors.isEmpty()) {
return FORM_VIEW;
}
int newArticleNo = writeService.write(writeReq);
req.setAttribute("newArticleNo", newArticleNo);
return "/WEB-INF/view/newArticleSuccess.jsp";
}
private WriteRequest createWriteRequest(User user, HttpServletRequest req) {
return new WriteRequest(
new Writer(user.getId(), user.getName()),
req.getParameter("title"),
req.getParameter("content"));
}
}
이 부분에서는 FORM_VIEW = "/WEB-INF/view/newArticleForm.jsp";
FORM_VIEW 이 부분을 newArticleForm 즉, 게시글을 작성할 수 있는 페이지로 이동하도록 경로를 설정하였습니다.
해당 클래스는 public class WriteArticleHandler implements CommandHandler
즉, CommandHandler 를 구현하는 구현클래스이며,
CommandHandler는
이 부분은 단순히 process를 정의를 한 인터페이스입니다.
<WriteArticleHandler.java>는 방금 본 인터페이스를 구현한 구현클래스이므로
프로세스를 정의해주어야 합니다.
여기서 프로세스는 두 가지 방식으로 나눌 수 있습니다.
get 방식일 때와 post 방식일 때를 나누어서 return 값을 다르게 주었습니다.
코드를 보면 get방식으로 들어오면 return processForm(req, res);으로 가라고 되어있고
아래 processForm은 그냥 return FORM_VIEW를 하도록 정의하고 있죠?
아까 전에 FORM_VIEW는 즉, 게시글을 작성할 수 있는 페이지로 이동하도록 경로를 설정하였습니다.
만약 포스트 방식으로 넘어온다면, return processSubmit(req, res); 으로 전달합니다.
processSubmit은 우리가 게시글을 작성을 할 때에 에러가 발생할 수 있는 부분이 있겠죠?
예를 들면 타이틀을 작성해야 하는 부분에 null 값이나 공백만 있는 경우들을 WriteRequest에 정의하였습니다.
맵으로 에러 객체를 생성하고, 로그인도 안되어있고, 내가 정의한 WriteRequest에 에러가 담긴다면
다시 글을 작성하도록 돌려주는 것입니다.
만약 에러가 없다면, 글을 성공적으로 작성했다는 newArticleSuccess.jsp 화면을 보여주게 됩니다.
WriteArticleService.java 부분을 볼까요?
<WriteArticleService.java>
package article.command;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import article.model.Writer;
import article.service.WriteArticleService;
import article.service.WriteRequest;
import auth.service.User;
import mvc.command.CommandHandler;
public class WriteArticleHandler implements CommandHandler {
private static final String FORM_VIEW = "/WEB-INF/view/newArticleForm.jsp";
private WriteArticleService writeService = new WriteArticleService();
@Override
public String process(HttpServletRequest req, HttpServletResponse res) {
if (req.getMethod().equalsIgnoreCase("GET")) {
return processForm(req, res);
} else if (req.getMethod().equalsIgnoreCase("POST")) {
return processSubmit(req, res);
} else {
res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return null;
}
}
private String processForm(HttpServletRequest req, HttpServletResponse res) {
return FORM_VIEW;
}
private String processSubmit(HttpServletRequest req, HttpServletResponse res) {
Map<String, Boolean> errors = new HashMap<>();
req.setAttribute("errors", errors);
User user = (User)req.getSession(false).getAttribute("authUser");
WriteRequest writeReq = createWriteRequest(user, req);
writeReq.validate(errors);
if (!errors.isEmpty()) {
return FORM_VIEW;
}
int newArticleNo = writeService.write(writeReq);
req.setAttribute("newArticleNo", newArticleNo);
return "/WEB-INF/view/newArticleSuccess.jsp";
}
private WriteRequest createWriteRequest(User user, HttpServletRequest req) {
return new WriteRequest(
new Writer(user.getId(), user.getName()),
req.getParameter("title"),
req.getParameter("content"));
}
}
여기서 중요한 부분은 Articel articel = toArticle(req)로 하여
아티클 객체를 만들고 아티클에는 (title & contect)가 있는데 그중에서 title만 넣는다는 뜻이며,
반대로 콘텐츠 테이블에는 에는 타이틀이 아니라 콘텐츠만 저장합니다!
그다음 ArticleDao로 넘어가게 됩니다. (-> 여기서 insert가 실행됩니다!!!!)
타이틀의 매게 변수,
콘텐츠의 매게 변수를 넣었을 때 각각의 ArticleDao에서
각 테이블로 insert를 해주는 쿼리문이 있습니다.
<Article.java>
package article.model;
import java.util.Date;
public class Article {
private Integer number;
private Writer writer;
private String title;
private Date regDate;
private Date modifiedDate;
private int readCount;
public Article(Integer number, Writer writer, String title,
Date regDate, Date modifiedDate, int readCount) {
this.number = number;
this.writer = writer;
this.title = title;
this.regDate = regDate;
this.modifiedDate = modifiedDate;
this.readCount = readCount;
}
public Integer getNumber() {
return number;
}
public Writer getWriter() {
return writer;
}
public String getTitle() {
return title;
}
public Date getRegDate() {
return regDate;
}
public Date getModifiedDate() {
return modifiedDate;
}
public int getReadCount() {
return readCount;
}
}
원래는 여기 칼럼의 이름과 매치를 해야 하는 것이 정석입니다.
지금은 수동으로 매칭을 해주기 때문에 달라도 괜찮습니다.
오라클과 다르게 mySQL은 테이블 정의를 할 때에
number가 시퀀스 정의 된 것처럼 자동으로 번호가 자동증가합니다.
mySQL 테이블을 생성할 때에 애초에
create table board.article (
article_no int auto_increment primary key,
이 부분에 auto_increment라고 지정을 했기 때문에
Article에 있는 테이블 칼럼에 null을 넣어도 번호가 자동으로 만들어집니다.
<ArticleDao.java>
package article.dao;
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.Date;
import java.util.List;
import article.model.Article;
import article.model.Writer;
import jdbc.JdbcUtil;
public class ArticleDao {
public Article insert(Connection conn, Article article) throws SQLException {
PreparedStatement pstmt = null;
Statement stmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement("insert into article "
+ "(writer_id, writer_name, title, regdate, moddate, read_cnt) "
+ "values (?,?,?,?,?,0)");
pstmt.setString(1, article.getWriter().getId());
pstmt.setString(2, article.getWriter().getName());
pstmt.setString(3, article.getTitle());
pstmt.setTimestamp(4, toTimestamp(article.getRegDate()));
pstmt.setTimestamp(5, toTimestamp(article.getModifiedDate()));
int insertedCount = pstmt.executeUpdate();
if (insertedCount > 0) {
stmt = conn.createStatement();
rs = stmt.executeQuery("select last_insert_id() from article");
if (rs.next()) {
Integer newNo = rs.getInt(1);
return new Article(newNo,
article.getWriter(),
article.getTitle(),
article.getRegDate(),
article.getModifiedDate(),
0);
}
}
return null;
} finally {
JdbcUtil.close(rs);
JdbcUtil.close(stmt);
JdbcUtil.close(pstmt);
}
}
private Timestamp toTimestamp(Date date) {
return new Timestamp(date.getTime());
}
public int selectCount(Connection conn) throws SQLException {
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery("select count(*) from article");
if (rs.next()) {
return rs.getInt(1);
}
return 0;
} finally {
JdbcUtil.close(rs);
JdbcUtil.close(stmt);
}
}
public List<Article> select(Connection conn, int startRow, int size) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement("select * from article " +
"order by article_no desc limit ?, ?");
pstmt.setInt(1, startRow);
pstmt.setInt(2, size);
rs = pstmt.executeQuery();
List<Article> result = new ArrayList<>();
while (rs.next()) {
result.add(convertArticle(rs));
}
return result;
} finally {
JdbcUtil.close(rs);
JdbcUtil.close(pstmt);
}
}
private Article convertArticle(ResultSet rs) throws SQLException {
return new Article(rs.getInt("article_no"),
new Writer(
rs.getString("writer_id"),
rs.getString("writer_name")),
rs.getString("title"),
toDate(rs.getTimestamp("regdate")),
toDate(rs.getTimestamp("moddate")),
rs.getInt("read_cnt"));
}
private Date toDate(Timestamp timestamp) {
return new Date(timestamp.getTime());
}
public Article selectById(Connection conn, int no) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(
"select * from article where article_no = ?");
pstmt.setInt(1, no);
rs = pstmt.executeQuery();
Article article = null;
if (rs.next()) {
article = convertArticle(rs);
}
return article;
} finally {
JdbcUtil.close(rs);
JdbcUtil.close(pstmt);
}
}
public void increaseReadCount(Connection conn, int no) throws SQLException {
try (PreparedStatement pstmt =
conn.prepareStatement(
"update article set read_cnt = read_cnt + 1 "+
"where article_no = ?")) {
pstmt.setInt(1, no);
pstmt.executeUpdate();
}
}
public int update(Connection conn, int no, String title) throws SQLException {
try (PreparedStatement pstmt =
conn.prepareStatement(
"update article set title = ?, moddate = now() "+
"where article_no = ?")) {
pstmt.setString(1, title);
pstmt.setInt(2, no);
return pstmt.executeUpdate();
}
}
}
Dao 코드 분석
- pstmt 라는 변수에 conn객체로 받아온 정보들을 쿼리문을 이용하여 하나씩 담아줍니다.
- pstmt.executeUpdate(); 이 부분은 데이터베이스에 업데이트를 해주는 부분입니다.
- Integer newNo = rs.getInt(1); //새로 insert 한 아이디 값을 가져옵니다.
<newArticleSuccess.jsp>
<%@ page contentType="text/html; charset=utf-8"%>
<!DOCTYPE html>
<html>
<head>
<title>게시글 등록</title>
</head>
<body>
게시글을 등록했습니다.
<br>
${ctxPath = pageContext.request.contextPath ; ''}
<a href="${ctxPath}/article/list.do">[게시글목록보기]</a>
<a href="${ctxPath}/article/read.do?no=${newArticleNo}">[게시글내용보기]</a>
</body>
</html>
여기서 핵심은
${newArticleNo} 이 부분입니다.
새롭게 추가된 아티클 번호가 보이게 됩니다 ^^
http://localhost:8080/chap21/article/list.do
게시글 목록 조회하기!
목록 조회도 각각의 핸들러와 서비스가 존재합니다.
<ReadArticleHandler.java>
package article.command;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import article.service.ArticleContentNotFoundException;
import article.service.ArticleData;
import article.service.ArticleNotFoundException;
import article.service.ReadArticleService;
import mvc.command.CommandHandler;
public class ReadArticleHandler implements CommandHandler {
private ReadArticleService readService = new ReadArticleService();
@Override
public String process(HttpServletRequest req, HttpServletResponse res)
throws Exception {
String noVal = req.getParameter("no");
int articleNum = Integer.parseInt(noVal);
try {
ArticleData articleData = readService.getArticle(articleNum, true);
req.setAttribute("articleData", articleData);
return "/WEB-INF/view/readArticle.jsp";
} catch (ArticleNotFoundException | ArticleContentNotFoundException e) {
req.getServletContext().log("no article", e);
res.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
}
<ReadArticleService.java>
package article.service;
import java.sql.Connection;
import java.sql.SQLException;
import article.dao.ArticleContentDao;
import article.dao.ArticleDao;
import article.model.Article;
import article.model.ArticleContent;
import jdbc.connection.ConnectionProvider;
public class ReadArticleService {
private ArticleDao articleDao = new ArticleDao();
private ArticleContentDao contentDao = new ArticleContentDao();
public ArticleData getArticle(int articleNum, boolean increaseReadCount) {
try (Connection conn = ConnectionProvider.getConnection()){
Article article = articleDao.selectById(conn, articleNum);
// 이 글이 있느냐? 있으면 가져욤 articleNum가 1일때 selectById 로 가보면
if (article == null) {
throw new ArticleNotFoundException();
}
ArticleContent content = contentDao.selectById(conn, articleNum);
if (content == null) {
throw new ArticleContentNotFoundException();
}
if (increaseReadCount) {
articleDao.increaseReadCount(conn, articleNum);
}
return new ArticleData(article, content);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
<readArticle.jsp>
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" tagdir="/WEB-INF/tags" %>
<!DOCTYPE html>
<html>
<head>
<title>게시글 읽기</title>
</head>
<body>
<table border="1" width="100%">
<tr>
<td>번호</td>
<td>${articleData.article.number}</td>
</tr>
<tr>
<td>작성자</td>
<td>${articleData.article.writer.name}</td>
</tr>
<tr>
<td>제목</td>
<td><c:out value='${articleData.article.title}' /></td>
</tr>
<tr>
<td>내용</td>
<td><u:pre value='${articleData.content}'/></td>
</tr>
<tr>
<td colspan="2">
<c:set var="pageNo" value="${empty param.pageNo ? '1' : param.pageNo}" />
<a href="list.do?pageNo=${pageNo}">[목록]</a>
<c:if test="${authUser.id == articleData.article.writer.id}">
<a href="modify.do?no=${articleData.article.number}">[게시글수정]</a>
<a href="delete.do?no=${articleData.article.number}">[게시글삭제]</a>
</c:if>
</td>
</tr>
</table>
</body>
</html>
여기서는 특이하게 아래처럼 객체를 가져와야 합니다.
<td>${articleData.article.writer.name}</td> 이렇게 getName으로 가져와야 합니다.
정의된 아티클데이터에 getArticle과 getContent를 정의하였습니다.
그렇다면 내용에도 어렵게 u태그 쓰지 말고
이렇게 간단히 쓸 수 있죠?
<td>${articleData.content()}</td>
이제 다시 readArticleHandler가서 받아온 데이터를 담아서 readArticle.jsp로 forward 시킵니다.
게시글 수정하기!
<ModifyArticleHandler.java>
package article.command;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import article.service.ArticleData;
import article.service.ArticleNotFoundException;
import article.service.ModifyArticleService;
import article.service.ModifyRequest;
import article.service.PermissionDeniedException;
import article.service.ReadArticleService;
import auth.service.User;
import mvc.command.CommandHandler;
public class ModifyArticleHandler implements CommandHandler {
private static final String FORM_VIEW = "/WEB-INF/view/modifyForm.jsp";
private ReadArticleService readService = new ReadArticleService();
private ModifyArticleService modifyService = new ModifyArticleService();
@Override
public String process(HttpServletRequest req, HttpServletResponse res)
throws Exception {
if (req.getMethod().equalsIgnoreCase("GET")) {
return processForm(req, res);
} else if (req.getMethod().equalsIgnoreCase("POST")) {
return processSubmit(req, res);
} else {
res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return null;
}
}
private String processForm(HttpServletRequest req, HttpServletResponse res)
throws IOException {
try {
String noVal = req.getParameter("no");
int no = Integer.parseInt(noVal);
ArticleData articleData = readService.getArticle(no, false);
User authUser = (User) req.getSession().getAttribute("authUser");
if (!canModify(authUser, articleData)) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
ModifyRequest modReq = new ModifyRequest(authUser.getId(), no,
articleData.getArticle().getTitle(),
articleData.getContent());
req.setAttribute("modReq", modReq);
return FORM_VIEW;
} catch (ArticleNotFoundException e) {
res.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
}
private boolean canModify(User authUser, ArticleData articleData) {
String writerId = articleData.getArticle().getWriter().getId();
return authUser.getId().equals(writerId);
}
private String processSubmit(HttpServletRequest req, HttpServletResponse res)
throws Exception {
User authUser = (User) req.getSession().getAttribute("authUser");
String noVal = req.getParameter("no");
int no = Integer.parseInt(noVal);
ModifyRequest modReq = new ModifyRequest(authUser.getId(), no,
req.getParameter("title"),
req.getParameter("content"));
req.setAttribute("modReq", modReq);
Map<String, Boolean> errors = new HashMap<>();
req.setAttribute("errors", errors);
modReq.validate(errors);
if (!errors.isEmpty()) {
return FORM_VIEW;
}
try {
modifyService.modify(modReq);
return "/WEB-INF/view/modifySuccess.jsp";
} catch (ArticleNotFoundException e) {
res.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
} catch (PermissionDeniedException e) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
}
}
User authUser = (User) req.getSession().getAttribute("authUser");
if (!canModify(authUser, articleData)) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
return null;
}
이 부분을 보면, 이 글을 작성한 사람의 이이디가 getiId()에 들어있으면 수정이 가능하고
authUser의 User가 데이터베이스의 글 작성자와 로그인을 한 사람이 같은 사람이면
true가 되기 때문에 예외를 발생시키지 않고 패스합니다.
또한
ModifyRequest에서 modreq 객체를 생성하고 여기서 만약에 접근을 하려면
이런 방식이기 때문에 jsp에서
제목을 가져오고 싶을 때 ${modReq.title}을 해주는 것입니다.
<ModifyRequest.java>
package article.service;
import java.util.Map;
public class ModifyRequest {
private String userId;
private int articleNumber;
private String title;
private String content;
public ModifyRequest(String userId, int articleNumber, String title, String content) {
this.userId = userId;
this.articleNumber = articleNumber;
this.title = title;
this.content = content;
}
public String getUserId() {
return userId;
}
public int getArticleNumber() {
return articleNumber;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
public void validate(Map<String, Boolean> errors) {
if (title == null || title.trim().isEmpty()) {
errors.put("title", Boolean.TRUE);
}
}
}
<modifyForm.jsp>
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>게시글 수정</title>
</head>
<body>
<form action="modify.do" method="post">
<input type="hidden" name="no" value="${modReq.articleNumber}">
<p>
번호:<br/>${modReq.articleNumber}
</p>
<p>
제목:<br/><input type="text" name="title" value="${modReq.title}">
<c:if test="${errors.title}">제목을 입력하세요.</c:if>
</p>
<p>
내용:<br/>
<textarea name="content" rows="5" cols="30">${modReq.content}</textarea>
</p>
<input type="submit" value="글 수정">
</form>
</body>
</html>
list.do
대망의 제일 어려운 list.do를 보겠습니다.
오늘 처음 접하는 부분이기 때문에 가볍게 보겠습니다.
/article/list.do=article.command.ListArticleHandler
<ListArticleHandler.java>
ListArticleHandler.java는 get방식으로 요청하고 파라미터가 페이지 수에 따라서 다르게 넘어갑니다.
package article.command;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import article.service.ArticlePage;
import article.service.ListArticleService;
import mvc.command.CommandHandler;
public class ListArticleHandler implements CommandHandler {
private ListArticleService listService = new ListArticleService();
@Override
public String process(HttpServletRequest req, HttpServletResponse res)
throws Exception {
String pageNoVal = req.getParameter("pageNo");
int pageNo = 1;
if (pageNoVal != null) {
pageNo = Integer.parseInt(pageNoVal);
}
ArticlePage articlePage = listService.getArticlePage(pageNo);
req.setAttribute("articlePage", articlePage);
return "/WEB-INF/view/listArticle.jsp";
}
}
아무런 페이지 번호가 없으면 기본값 = 1
페이지 넘버가 1이 아니면, (null 이 아니고 만약 다른 수라면) 그 수를 넣어라입니다.
그다음 서비스 아티클 페이지로 넘어갑니다.
<ArticlePage.java>
자바빈 객체를 만드는 복잡한 페이지입니다.(아티클페이지 객체 만들기)
package article.service;
import java.util.List;
import article.model.Article;
public class ArticlePage {
private int total; //11
private int currentPage;//2
private List<Article> content; //아티클 담기
private int totalPages;//2
private int startPage;//1
private int endPage;//2
public ArticlePage(int total, int currentPage, int size, List<Article> content) {
//처음에 토탈이 들어온다고 가정 = 11 , 사이즈는 10
this.total = total;//11
this.currentPage = currentPage;//2
this.content = content;
if (total == 0) {//토탈이 0이 아니면 스킵
totalPages = 0;
startPage = 0;
endPage = 0;
} else { //토탈에 지금 11 이 있으니까 11/10 = 1 이다.
totalPages = total / size; // 토탈 페이지에는 1이 들어감
if (total % size > 0) {//토탈에서 나머지를 구한다. 11%10=1
totalPages++; //토탈이 0보다 크니까 totalPage에 1을 올려줌 즉, totalPage는 2가 됩니다.
}
int modVal = currentPage % 5; //현재 페이지: 2 ==> 2 % 5 = 2
startPage = currentPage / 5 * 5 + 1;//현재 페이지: 2 => 2 / 5*5+1 = 1
if (modVal == 0) startPage -= 5; //modVal이 0이 아니니까 이부분은 적용이 안됩니다.
endPage = startPage + 4;
if (endPage > totalPages) endPage = totalPages;
//endPage가 totalPages보다 크면 endPage에 totalPages를 넣습니다.
}
}
public int getTotal() {
return total;
}
public boolean hasNoArticles() {
return total == 0;
}
public boolean hasArticles() {
return total > 0;
}
public int getCurrentPage() {
return currentPage;
}
public int getTotalPages() {
return totalPages;
}
public List<Article> getContent() {
return content;
}
public int getStartPage() {
return startPage;
}
public int getEndPage() {
return endPage;
}
}
이렇게 다하고 아티클페이지 객체를 핸들러에게 다시 넘깁니다.
<ListArticleService.java>
package article.service;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import article.dao.ArticleDao;
import article.model.Article;
import jdbc.connection.ConnectionProvider;
public class ListArticleService {
private ArticleDao articleDao = new ArticleDao();
private int size = 10;
public ArticlePage getArticlePage(int pageNum) {
try (Connection conn = ConnectionProvider.getConnection()) {
int total = articleDao.selectCount(conn);
List<Article> content = articleDao.select(
conn, (pageNum - 1) * size, size);
return new ArticlePage(total, pageNum, size, content);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
전체 글 : total 값을 가져와서
Dao의 select를 하는데
conn에 (pageNu-1)
size는 각 페이지의 글의 개수입니다.
즉, 한 페이지당 글을 10개를 넣을 수 있죠!
만약 두 번째 페이지로 넘어간다고 한다면
conn에 (2-1)
desc 내림차순 (limit 시작인덱스, 가져올 개수)
0번째 인덱스 내림차순이니까 11부터, 10자리까지 2까지 출력이 보입니다.
modVal 이 부분은 페이지의 페이지의 다음을 보기 위해서 5개씩 쪼개는 부분입니다.
.
<listArticle.jsp>
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>게시글 목록</title>
</head>
<body>
<table border="1">
<tr>
<td colspan="4"><a href="write.do">[게시글쓰기]</a></td>
</tr>
<tr>
<td>번호</td>
<td>제목</td>
<td>작성자</td>
<td>조회수</td>
</tr>
<c:if test="${articlePage.hasNoArticles()}">
<tr>
<td colspan="4">게시글이 없습니다.</td>
</tr>
</c:if>
<!-- 아티클 객체를 아티클페이지의 컨텐츠에서 가져옵니다. 그후 테이블의 article의 번호를 와 그 외를 출력합니다. -->
<c:forEach var="article" items="${articlePage.content}">
<tr>
<td>${article.number}</td>
<td>
<a href="read.do?no=${article.number}&pageNo=${articlePage.currentPage}">
<c:out value="${article.title}"/>
</a>
</td>
<td>${article.writer.name}</td>
<td>${article.readCount}</td>
</tr>
</c:forEach>
<c:if test="${articlePage.hasArticles()}">
<tr>
<td colspan="4">
<c:if test="${articlePage.startPage > 5}">
<a href="list.do?pageNo=${articlePage.startPage - 5}">[이전]</a>
</c:if>
<c:forEach var="pNo"
begin="${articlePage.startPage}"
end="${articlePage.endPage}">
<a href="list.do?pageNo=${pNo}">[${pNo}]</a>
</c:forEach>
<c:if test="${articlePage.endPage < articlePage.totalPages}">
<a href="list.do?pageNo=${articlePage.startPage + 5}">[다음]</a>
</c:if>
</td>
</tr>
</c:if>
</table>
</body>
</html>
<c:forEach var="article" items="${articlePage.content}">
이부분 아래의 부분에는 그냥 테이블을 쭉 출력해 주는 부분입니다.
페이지의 개수가 크지 않는 정도만큼의 테이블만 보여줍니다.
<a href="list.do?pageNo=${articlePage.startPage + 5}">[다음]</a>
위의 예시처럼 [1] [2] 이 부분이 두 개밖에 없기 때문에 [다음] 이 안 나옵니다.
만약 [1] [2] [3] [4] [5] 이후까지 데이터가 많다면 그 이상은 [다음]으로 표현하게 됩니다.
오늘은 여기까지 JSP의 게시판 기능인 쓰기, 목록보기, 조회하기. 수정하기까지 보았습니다.
게시글 삭제하기는 modify핸들러에서 글을 수정할 때 Dao에 있는 쿼리문을 delete를 추가해 주면 됩니다.
이상으로 JSP의 게시판 기능 기초에 대해 알아보았습니다.
오늘도 수고 많으셨습니다.
감사합니다.
'주니어 기초 코딩공부 > JSP 기초' 카테고리의 다른 글
[JSTL] 비교 연산할 때 소수점 없애기 formatNumber (0) | 2023.11.08 |
---|---|
JSP C태그_<c:out value="${값}"/> (0) | 2023.08.03 |
[JSP] 로그인 기능 구현 기초 설명 (0) | 2023.01.31 |
[JSP] 회원제 게시판 구현 (0) | 2023.01.31 |
[JSP] 필터란 무엇인가 (0) | 2023.01.30 |