[이클립스/웹개발] MVC를 활용한 답변형 게시판 구현 (7) - 게시판 페이징 기능
페이징 기능 구현 방법
한 페이지마다 10개의 글이 표시되고, 이 페이지 10개가 모여 한 개의 섹션(section)이 된다. 첫 번째 섹션은 첫 번째 페이지부터 열 번째 페이지까지이다. 두 번째 섹션은 열한 번째 페이지부터 스무 번째 페이지까지이다. 따라서 사용자가 글 목록 페이지에서 [2]를 클릭하면 브라우저는 서버에 section 값으로는 1을, pageNum값으로는 2를 전송한다. 그리고 글 목록에는 두 번째 페이지에 해당하는 글인 11에서 20번째 글을 테이블에서 조회한 후 표시한다.
페이징 기능을 추가한 글 목록 조회 SQL문
SELECT * FROM(
SELECT ROWNUM AS recNum,
LVL,
articleNO,
parentNO,
title,
content,
id,
writedate
FROM(
SELECT LEVEL AS LVL,
articleNO,
parnetNO,
title,
content,
id,
writedate,
FROM t_board
START WITH parentNO = 0
CONNECT BY PRIOR articleNO = parentNO
ORDER SIBLINGS BY articleNO DESC
))
WHERE recNum BETWEEN(section-1)*100+(pageNum-1)*10+1 and (section-1)*100+pageNum*10;
페이징 기능을 구현하기 위해 서브 쿼리문과 오라클에서 제공하는 가상 컬럼인 ROWNUM을 이용한다. ROWNUM은 SELECT문으로 조회한 레코드 목록에 대해 오라클 자체에서 순서를 부여하여 레코드 번호를 순서대로 할당한다.
서브쿼리문의 실행 순서는 다음과 같다.
- 기존 계층형 구조로 글 목록을 일단 조회한다.
- 그 결과에 대해 다시 ROWNUM(recNum)이 표시되도록 서브 쿼리문을 이용해 다시 한번 조회한다.
- ROWNUM이 표시된 두 번째 결과에서 section과 pageNum으로 계산된 where절의 between 연산자 사이의 값에 해당하는 ROWNUM이 있는 레코드들만 최종적으로 조회한다.
1. BoardController 클래스를 다음과 같이 작성
/listArticle.do로 최초 요청 시 section과 pagenNum의 기본값을 1로 초기화, 컨트롤러에서는 전달된 section과 pageNum을 HashMap에 저장한 후 DAO로 전달한다.
package sec03.brd08;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@WebServlet("/board/*")
public class BoardController extends HttpServlet {
private static String ARTICLE_IMAGE_REPO = "C:\\board\\article_image";
BoardService boardService;
ArticleVO articleVO;
public void init(ServletConfig config) throws ServletException {
boardService = new BoardService();
articleVO = new ArticleVO();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String nextPage = "";
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
HttpSession session;
String action = request.getPathInfo();
System.out.println("action:" + action);
try {
List<ArticleVO> articlesList = new ArrayList<ArticleVO>();
if (action==null){
String _section=request.getParameter("section");
String _pageNum=request.getParameter("pageNum");
int section = Integer.parseInt(((_section==null)? "1":_section) );
int pageNum = Integer.parseInt(((_pageNum==null)? "1":_pageNum));
Map<String, Integer> pagingMap = new HashMap<String, Integer>();
pagingMap.put("section", section);
pagingMap.put("pageNum", pageNum);
Map articlesMap=boardService.listArticles(pagingMap);
articlesMap.put("section", section);
articlesMap.put("pageNum", pageNum);
request.setAttribute("articlesMap", articlesMap);
nextPage = "/board07/ listArticles.jsp";
}else if(action.equals("/listArticles.do")){
String _section=request.getParameter("section");
String _pageNum=request.getParameter("pageNum");
int section = Integer.parseInt(((_section==null)? "1":_section) );
int pageNum = Integer.parseInt(((_pageNum==null)? "1":_pageNum));
Map pagingMap=new HashMap();
pagingMap.put("section", section);
pagingMap.put("pageNum", pageNum);
Map articlesMap=boardService.listArticles(pagingMap);
articlesMap.put("section", section);
articlesMap.put("pageNum", pageNum);
request.setAttribute("articlesMap", articlesMap);
nextPage = "/board07/listArticles.jsp";
} else if (action.equals("/articleForm.do")) {
nextPage = "/board07/articleForm.jsp";
} else if (action.equals("/addArticle.do")) {
int articleNO = 0;
Map<String, String> articleMap = upload(request, response);
String title = articleMap.get("title");
String content = articleMap.get("content");
String imageFileName = articleMap.get("imageFileName");
articleVO.setParentNO(0);
articleVO.setId("hong");
articleVO.setTitle(title);
articleVO.setContent(content);
articleVO.setImageFileName(imageFileName);
articleNO = boardService.addArticle(articleVO);
if (imageFileName != null && imageFileName.length() != 0) {
File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
destDir.mkdirs();
FileUtils.moveFileToDirectory(srcFile, destDir, true);
}
PrintWriter pw = response.getWriter();
pw.print("<script>" + " alert('새글을 추가했습니다.');" + " location.href='" + request.getContextPath()
+ "/board/listArticles.do';" + "</script>");
return;
} else if (action.equals("/viewArticle.do")) {
String articleNO = request.getParameter("articleNO");
articleVO = boardService.viewArticle(Integer.parseInt(articleNO));
request.setAttribute("article", articleVO);
nextPage = "/board07/viewArticle.jsp";
} else if (action.equals("/modArticle.do")) {
Map<String, String> articleMap = upload(request, response);
int articleNO = Integer.parseInt(articleMap.get("articleNO"));
articleVO.setArticleNO(articleNO);
String title = articleMap.get("title");
String content = articleMap.get("content");
String imageFileName = articleMap.get("imageFileName");
articleVO.setParentNO(0);
articleVO.setId("hong");
articleVO.setTitle(title);
articleVO.setContent(content);
articleVO.setImageFileName(imageFileName);
boardService.modArticle(articleVO);
if (imageFileName != null && imageFileName.length() != 0) {
String originalFileName = articleMap.get("originalFileName");
File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
destDir.mkdirs();
FileUtils.moveFileToDirectory(srcFile, destDir, true);
;
File oldFile = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO + "\\" + originalFileName);
oldFile.delete();
}
PrintWriter pw = response.getWriter();
pw.print("<script>" + " alert('글을 수정했습니다.');" + " location.href='" + request.getContextPath()
+ "/board/viewArticle.do?articleNO=" + articleNO + "';" + "</script>");
return;
} else if (action.equals("/removeArticle.do")) {
int articleNO = Integer.parseInt(request.getParameter("articleNO"));
List<Integer> articleNOList = boardService.removeArticle(articleNO);
for (int _articleNO : articleNOList) {
File imgDir = new File(ARTICLE_IMAGE_REPO + "\\" + _articleNO);
if (imgDir.exists()) {
FileUtils.deleteDirectory(imgDir);
}
}
PrintWriter pw = response.getWriter();
pw.print("<script>" + " alert('글을 삭제했습니다.');" + " location.href='" + request.getContextPath()
+ "/board/listArticles.do';" + "</script>");
return;
} else if (action.equals("/replyForm.do")) {
int parentNO = Integer.parseInt(request.getParameter("parentNO"));
session = request.getSession();
session.setAttribute("parentNO", parentNO);
nextPage = "/board07/replyForm.jsp";
} else if (action.equals("/addReply.do")) {
session = request.getSession();
int parentNO = (Integer) session.getAttribute("parentNO");
session.removeAttribute("parentNO");
Map<String, String> articleMap = upload(request, response);
String title = articleMap.get("title");
String content = articleMap.get("content");
String imageFileName = articleMap.get("imageFileName");
articleVO.setParentNO(parentNO);
articleVO.setId("lee");
articleVO.setTitle(title);
articleVO.setContent(content);
articleVO.setImageFileName(imageFileName);
int articleNO = boardService.addReply(articleVO);
if (imageFileName != null && imageFileName.length() != 0) {
File srcFile = new File(ARTICLE_IMAGE_REPO + "\\" + "temp" + "\\" + imageFileName);
File destDir = new File(ARTICLE_IMAGE_REPO + "\\" + articleNO);
destDir.mkdirs();
FileUtils.moveFileToDirectory(srcFile, destDir, true);
}
PrintWriter pw = response.getWriter();
pw.print("<script>" + " alert('답글을 추가했습니다.');" + " location.href='" + request.getContextPath()
+ "/board/viewArticle.do?articleNO="+articleNO+"';" + "</script>");
return;
}else {
nextPage = "/board07/listArticles.jsp";
}
RequestDispatcher dispatch = request.getRequestDispatcher(nextPage);
dispatch.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
private Map<String, String> upload(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Map<String, String> articleMap = new HashMap<String, String>();
String encoding = "utf-8";
File currentDirPath = new File(ARTICLE_IMAGE_REPO);
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(currentDirPath);
factory.setSizeThreshold(1024 * 1024);
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List items = upload.parseRequest(new ServletRequestContext(request));
for (int i = 0; i < items.size(); i++) {
FileItem fileItem = (FileItem) items.get(i);
if (fileItem.isFormField()) {
System.out.println(fileItem.getFieldName() + "=" + fileItem.getString(encoding));
articleMap.put(fileItem.getFieldName(), fileItem.getString(encoding));
} else {
System.out.println("파라미터명:" + fileItem.getFieldName());
//System.out.println("파일명:" + fileItem.getName());
System.out.println("파일크기:" + fileItem.getSize() + "bytes");
//articleMap.put(fileItem.getFieldName(), fileItem.getName());
if (fileItem.getSize() > 0) {
int idx = fileItem.getName().lastIndexOf("\\");
if (idx == -1) {
idx = fileItem.getName().lastIndexOf("/");
}
String fileName = fileItem.getName().substring(idx + 1);
System.out.println("파일명:" + fileName);
articleMap.put(fileItem.getFieldName(), fileName); //익스플로러에서 업로드 파일의 경로 제거 후 map에 파일명 저장);
File uploadFile = new File(currentDirPath + "\\temp\\" + fileName);
fileItem.write(uploadFile);
} // end if
} // end if
} // end for
} catch (Exception e) {
e.printStackTrace();
}
return articleMap;
}
}
2. BoardService 클레스를 다음과 같이 구현
페이징 기능에 필요한 글 목록과 전체 글 수를 각각 조회할 수 있도록 다음과 같이 구현, HashMap을 생성한 후 조회한 두 정보를 각각 속성으로 저장
JSP로 넘겨줘야 할 정보가 많은 경우에는 각 request에 바인딩해서 넘겨도 되지만 HashMap을 사용해 같은 종류의 정보를 묶어서 넘기면 편리하다.
package sec03.brd08;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BoardService {
BoardDAO boardDAO;
public BoardService() {
boardDAO = new BoardDAO();
}
public Map listArticles(Map<String, Integer> pagingMap) {
Map articlesMap = new HashMap();
List<ArticleVO> articlesList = boardDAO.selectAllArticles(pagingMap);
int totArticles = boardDAO.selectTotArticles();
articlesMap.put("articlesList", articlesList);
articlesMap.put("totArticles", totArticles);
//articlesMap.put("totArticles", 170);
return articlesMap;
}
public List<ArticleVO> listArticles() {
List<ArticleVO> articlesList = boardDAO.selectAllArticles();
return articlesList;
}
public int addArticle(ArticleVO article) {
return boardDAO.insertNewArticle(article);
}
public ArticleVO viewArticle(int articleNO) {
ArticleVO article = null;
article = boardDAO.selectArticle(articleNO);
return article;
}
public void modArticle(ArticleVO article) {
boardDAO.updateArticle(article);
}
public List<Integer> removeArticle(int articleNO) {
List<Integer> articleNOList = boardDAO.selectRemovedArticles(articleNO);
boardDAO.deleteArticle(articleNO);
return articleNOList;
}
public int addReply(ArticleVO article) {
return boardDAO.insertNewArticle(article);
}
}
3. BoardDAO를 다음과 같이 작성
전달받은 section과 pageNum값을 이용해 SQL문으로 조회
package sec03.brd08;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class BoardDAO {
private DataSource dataFactory;
Connection conn;
PreparedStatement pstmt;
public BoardDAO() {
try {
Context ctx = new InitialContext();
Context envContext = (Context) ctx.lookup("java:/comp/env");
dataFactory = (DataSource) envContext.lookup("jdbc/oracle");
} catch (Exception e) {
e.printStackTrace();
}
}
public List selectAllArticles(Map pagingMap){
List articlesList = new ArrayList();
int section = (Integer)pagingMap.get("section");
int pageNum=(Integer)pagingMap.get("pageNum");
try{
conn = dataFactory.getConnection();
String query ="SELECT * FROM ( "
+ "select ROWNUM as recNum,"+"LVL,"
+"articleNO,"
+"parentNO,"
+"title,"
+"id,"
+"writeDate"
+" from (select LEVEL as LVL, "
+"articleNO,"
+"parentNO,"
+"title,"
+"id,"
+"writeDate"
+" from t_board"
+" START WITH parentNO=0"
+" CONNECT BY PRIOR articleNO = parentNO"
+" ORDER SIBLINGS BY articleNO DESC)"
+") "
+" where recNum between(?-1)*100+(?-1)*10+1 and (?-1)*100+?*10";
System.out.println(query);
pstmt= conn.prepareStatement(query);
pstmt.setInt(1, section);
pstmt.setInt(2, pageNum);
pstmt.setInt(3, section);
pstmt.setInt(4, pageNum);
ResultSet rs =pstmt.executeQuery();
while(rs.next()){
int level = rs.getInt("lvl");
int articleNO = rs.getInt("articleNO");
int parentNO = rs.getInt("parentNO");
String title = rs.getString("title");
String id = rs.getString("id");
Date writeDate= rs.getDate("writeDate");
ArticleVO article = new ArticleVO();
article.setLevel(level);
article.setArticleNO(articleNO);
article.setParentNO(parentNO);
article.setTitle(title);
article.setId(id);
article.setWriteDate(writeDate);
articlesList.add(article);
} //end while
rs.close();
pstmt.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return articlesList;
}
public List selectAllArticles() {
List articlesList = new ArrayList();
try {
conn = dataFactory.getConnection();
String query = "SELECT LEVEL,articleNO,parentNO,title,content,id,writeDate" + " from t_board"
+ " START WITH parentNO=0" + " CONNECT BY PRIOR articleNO=parentNO"
+ " ORDER SIBLINGS BY articleNO DESC";
System.out.println(query);
pstmt = conn.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int level = rs.getInt("level");
int articleNO = rs.getInt("articleNO");
int parentNO = rs.getInt("parentNO");
String title = rs.getString("title");
String content = rs.getString("content");
String id = rs.getString("id");
Date writeDate = rs.getDate("writeDate");
ArticleVO article = new ArticleVO();
article.setLevel(level);
article.setArticleNO(articleNO);
article.setParentNO(parentNO);
article.setTitle(title);
article.setContent(content);
article.setId(id);
article.setWriteDate(writeDate);
articlesList.add(article);
}
rs.close();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return articlesList;
}
private int getNewArticleNO() {
try {
conn = dataFactory.getConnection();
String query = "SELECT max(articleNO) from t_board ";
System.out.println(query);
pstmt = conn.prepareStatement(query);
ResultSet rs = pstmt.executeQuery(query);
if (rs.next())
return (rs.getInt(1) + 1);
rs.close();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public int insertNewArticle(ArticleVO article) {
int articleNO = getNewArticleNO();
try {
conn = dataFactory.getConnection();
int parentNO = article.getParentNO();
String title = article.getTitle();
String content = article.getContent();
String id = article.getId();
String imageFileName = article.getImageFileName();
String query = "INSERT INTO t_board (articleNO, parentNO, title, content, imageFileName, id)"
+ " VALUES (?, ? ,?, ?, ?, ?)";
System.out.println(query);
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, articleNO);
pstmt.setInt(2, parentNO);
pstmt.setString(3, title);
pstmt.setString(4, content);
pstmt.setString(5, imageFileName);
pstmt.setString(6, id);
pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return articleNO;
}
public ArticleVO selectArticle(int articleNO) {
ArticleVO article = new ArticleVO();
try {
conn = dataFactory.getConnection();
String query = "select articleNO,parentNO,title,content, NVL(imageFileName, 'null') as imageFileName, id, writeDate" + " from t_board"
+ " where articleNO=?";
System.out.println(query);
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, articleNO);
ResultSet rs = pstmt.executeQuery();
rs.next();
int _articleNO = rs.getInt("articleNO");
int parentNO = rs.getInt("parentNO");
String title = rs.getString("title");
String content = rs.getString("content");
String imageFileName = URLEncoder.encode(rs.getString("imageFileName"), "UTF-8"); //파일이름에 특수문자가 있을 경우 인코딩합니다.
if(imageFileName.equals("null")) {
imageFileName = null;
}
String id = rs.getString("id");
Date writeDate = rs.getDate("writeDate");
article.setArticleNO(_articleNO);
article.setParentNO(parentNO);
article.setTitle(title);
article.setContent(content);
article.setImageFileName(imageFileName);
article.setId(id);
article.setWriteDate(writeDate);
rs.close();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return article;
}
public void updateArticle(ArticleVO article) {
int articleNO = article.getArticleNO();
String title = article.getTitle();
String content = article.getContent();
String imageFileName = article.getImageFileName();
try {
conn = dataFactory.getConnection();
String query = "update t_board set title=?,content=?";
if (imageFileName != null && imageFileName.length() != 0) {
query += ",imageFileName=?";
}
query += " where articleNO=?";
System.out.println(query);
pstmt = conn.prepareStatement(query);
pstmt.setString(1, title);
pstmt.setString(2, content);
if (imageFileName != null && imageFileName.length() != 0) {
pstmt.setString(3, imageFileName);
pstmt.setInt(4, articleNO);
} else {
pstmt.setInt(3, articleNO);
}
pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void deleteArticle(int articleNO) {
try {
conn = dataFactory.getConnection();
String query = "DELETE FROM t_board ";
query += " WHERE articleNO in (";
query += " SELECT articleNO FROM t_board ";
query += " START WITH articleNO = ?";
query += " CONNECT BY PRIOR articleNO = parentNO )";
System.out.println(query);
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, articleNO);
pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public List<Integer> selectRemovedArticles(int articleNO) {
List<Integer> articleNOList = new ArrayList<Integer>();
try {
conn = dataFactory.getConnection();
String query = "SELECT articleNO FROM t_board ";
query += " START WITH articleNO = ?";
query += " CONNECT BY PRIOR articleNO = parentNO";
System.out.println(query);
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, articleNO);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
articleNO = rs.getInt("articleNO");
articleNOList.add(articleNO);
}
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return articleNOList;
}
public int selectTotArticles() {
try {
conn = dataFactory.getConnection();
String query = "select count(articleNO) from t_board ";
System.out.println(query);
pstmt = conn.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
if (rs.next())
return (rs.getInt(1));
rs.close();
pstmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
4. ArticleVO를 작성
package sec03.brd08;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Date;
public class ArticleVO {
private int level;
private int articleNO;
private int parentNO;
private String title;
private String content;
private String imageFileName;
private String id;
private Date writeDate;
public ArticleVO() {
}
public ArticleVO(int level, int articleNO, int parentNO, String title, String content, String imageFileName,
String id) {
super();
this.level = level;
this.articleNO = articleNO;
this.parentNO = parentNO;
this.title = title;
this.content = content;
this.imageFileName = imageFileName;
this.id = id;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getArticleNO() {
return articleNO;
}
public void setArticleNO(int articleNO) {
this.articleNO = articleNO;
}
public int getParentNO() {
return parentNO;
}
public void setParentNO(int parentNO) {
this.parentNO = parentNO;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getImageFileName() {
try {
if (imageFileName != null && imageFileName.length() != 0) {
imageFileName = URLDecoder.decode(imageFileName, "UTF-8");
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return imageFileName;
}
public void setImageFileName(String imageFileName) {
try {
if(imageFileName!=null && imageFileName.length()!=0) {
this.imageFileName = URLEncoder.encode(imageFileName, "UTF-8"); //파일이름에 특수문자가 있을 경우 인코딩합니다.
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getWriteDate() {
return writeDate;
}
public void setWriteDate(Date writeDate) {
this.writeDate = writeDate;
}
}
5. common패키지 생성 후 FileDownloadController클래스를 다음과 같이 작성
package sec03.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/download.do")
public class FileDownloadController extends HttpServlet {
private static String ARTICLE_IMAGE_REPO = "C:\\board\\article_image";
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
String imageFileName = (String) request.getParameter("imageFileName");
String articleNO = request.getParameter("articleNO");
System.out.println("imageFileName=" + imageFileName);
OutputStream out = response.getOutputStream();
String path = ARTICLE_IMAGE_REPO + "\\" + articleNO + "\\" + imageFileName;
File imageFile = new File(path);
response.setHeader("Cache-Control", "no-cache");
response.addHeader("Content-disposition", "attachment;fileName=" + imageFileName);
FileInputStream in = new FileInputStream(imageFile);
byte[] buffer = new byte[1024 * 8];
while (true) {
int count = in.read(buffer);
if (count == -1)
break;
out.write(buffer, 0, count);
}
in.close();
out.close();
}
}
6. 화면을 구현하는 JSP 페이지인 listArticles.jsp를 다음과 같이 작성
전체 글 수(totArticles)가 100개가 넘는 경우, 100개인 경우, 100개를 넘지 않는 경우로 나누어 페이지 번호를 표시하도록 구현한다. 전체 글 수 가 100개가 넘지 않으면 전체 글 수를 10으로 나눈 몫에 1을 더한 값이 페이지 번호로 표시된다. 예를 들어 전체 글 수가 13개이면 10으로 나누었을 때의 몫인 1에 1을 더해 2가 페이지 번호로 표시된다. 만약 전체 글 수가 100개일 때는 정확히 10개의 페이지가 표시되며, 100개를 넘을 때는 다음 section으로 이동할 수 있도록 마지막 페이지 번호 앞에 next를 표시한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<c:set var="articlesList" value="${articlesMap.articlesList}" />
<c:set var="totArticles" value="${articlesMap.totArticles}" />
<c:set var="section" value="${articlesMap.section}" />
<c:set var="pageNum" value="${articlesMap.pageNum}" />
<%
request.setCharacterEncoding("UTF-8");
%>
<!DOCTYPE html>
<html>
<head>
<style>
.no-uline {text-decoration:none;}
.sel-page{text-decoration:none;color:red;}
.cls1 {text-decoration:none;}
.cls2{text-align:center; font-size:30px;}
</style>
<meta charset="UTF-8">
<title>글목록창</title>
</head>
<body>
<table align="center" border="1" width="80%" >
<tr height="10" align="center" bgcolor="lightgreen">
<td >글번호</td>
<td >작성자</td>
<td >제목</td>
<td >작성일</td>
</tr>
<c:choose>
<c:when test="${empty articlesList}" >
<tr height="10">
<td colspan="4">
<p align="center">
<b><span style="font-size:9pt;">등록된 글이 없습니다.</span></b>
</p>
</td>
</tr>
</c:when>
<c:when test="${!empty articlesList}" >
<c:forEach var="article" items="${articlesList }" varStatus="articleNum" >
<tr align="center">
<td width="5%">${articleNum.count}</td>
<td width="10%">${article.id }</td>
<td align='left' width="35%">
<span style="padding-right:30px"></span>
<c:choose>
<c:when test='${article.level > 1 }'>
<c:forEach begin="1" end="${article.level }" step="1">
<span style="padding-left:10px"></span>
</c:forEach>
<span style="font-size:12px;">[답변]</span>
<a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title}</a>
</c:when>
<c:otherwise>
<a class='cls1' href="${contextPath}/board/viewArticle.do?articleNO=${article.articleNO}">${article.title }</a>
</c:otherwise>
</c:choose>
</td>
<td width="10%"><fmt:formatDate value="${article.writeDate}" /></td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</table>
<div class="cls2">
<c:if test="${totArticles != null }" >
<c:choose>
<c:when test="${totArticles >100 }"> <!-- 글 개수가 100 초과인경우 -->
<c:forEach var="page" begin="1" end="10" step="1" >
<c:if test="${section >1 && page==1 }">
<a class="no-uline" href="${contextPath }/board/listArticles.do?section=${section-1}&pageNum=${(section-1)*10 +1 }"> pre </a>
</c:if>
<a class="no-uline" href="${contextPath }/board/listArticles.do?section=${section}&pageNum=${page}">${(section-1)*10 +page } </a>
<c:if test="${page ==10 }">
<a class="no-uline" href="${contextPath }/board/listArticles.do?section=${section+1}&pageNum=${section*10+1}"> next</a>
</c:if>
</c:forEach>
</c:when>
<c:when test="${totArticles ==100 }" > <!--등록된 글 개수가 100개인경우 -->
<c:forEach var="page" begin="1" end="10" step="1" >
<a class="no-uline" href="#">${page } </a>
</c:forEach>
</c:when>
<c:when test="${totArticles< 100 }" > <!--등록된 글 개수가 100개 미만인 경우 -->
<c:forEach var="page" begin="1" end="${totArticles/10 +1}" step="1" >
<c:choose>
<c:when test="${page==pageNum }">
<a class="sel-page" href="${contextPath }/board/listArticles.do?section=${section}&pageNum=${page}">${page } </a>
</c:when>
<c:otherwise>
<a class="no-uline" href="${contextPath }/board/listArticles.do?section=${section}&pageNum=${page}">${page } </a>
</c:otherwise>
</c:choose>
</c:forEach>
</c:when>
</c:choose>
</c:if>
</div>
<br><br>
<a class="cls1" href="${contextPath}/board/articleForm.do"><p class="cls2">글쓰기</p></a>
</body>
</html>
7. 새글쓰기창 articleForm.jsp를 다음과 같이 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
request.setCharacterEncoding("UTF-8");
%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<head>
<meta charset="UTF-8">
<title>글쓰기창</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#preview').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
function backToList(obj){
obj.action="${contextPath}/board/listArticles.do";
obj.submit();
}
</script>
<title>새글 쓰기 창</title>
</head>
<body>
<h1 style="text-align:center">새글 쓰기</h1>
<form name="articleForm" method="post" action="${contextPath}/board/addArticle.do" enctype="multipart/form-data">
<table border="0" align="center">
<tr>
<td align="right">글제목: </td>
<td colspan="2"><input type="text" size="67" maxlength="500" name="title" /></td>
</tr>
<tr>
<td align="right" valign="top"><br>글내용: </td>
<td colspan=2><textarea name="content" rows="10" cols="65" maxlength="4000"></textarea> </td>
</tr>
<tr>
<td align="right">이미지파일 첨부: </td>
<td> <input type="file" name="imageFileName" onchange="readURL(this);" /></td>
<td><img id="preview" src="#" width=200 height=200/></td>
</tr>
<tr>
<td align="right"> </td>
<td colspan="2">
<input type="submit" value="글쓰기" />
<input type=button value="목록보기"onClick="backToList(this.form)" />
</td>
</tr>
</table>
</form>
</body>
</html>
8. 상세정보창 viewArticle.jsp를 다음과 같이 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
request.setCharacterEncoding("UTF-8");
%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<head>
<meta charset="UTF-8">
<title>글보기</title>
<style>
#tr_btn_modify{
display:none;
}
</style>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" >
function backToList(obj){
obj.action="${contextPath}/board/listArticles.do";
obj.submit();
}
function fn_enable(obj){
document.getElementById("i_title").disabled=false;
document.getElementById("i_content").disabled=false;
document.getElementById("i_imageFileName").disabled=false;
document.getElementById("tr_btn_modify").style.display="block";
document.getElementById("tr_btn").style.display="none";
}
function fn_modify_article(obj){
obj.action="${contextPath}/board/modArticle.do";
obj.submit();
}
function fn_remove_article(url,articleNO){
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", url);
var articleNOInput = document.createElement("input");
articleNOInput.setAttribute("type","hidden");
articleNOInput.setAttribute("name","articleNO");
articleNOInput.setAttribute("value", articleNO);
form.appendChild(articleNOInput);
document.body.appendChild(form);
form.submit();
}
function fn_reply_form(url, parentNO){
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", url);
var parentNOInput = document.createElement("input");
parentNOInput.setAttribute("type","hidden");
parentNOInput.setAttribute("name","parentNO");
parentNOInput.setAttribute("value", parentNO);
form.appendChild(parentNOInput);
document.body.appendChild(form);
form.submit();
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#preview').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
</script>
</head>
<body>
<form name="frmArticle" method="post" action="${contextPath}" enctype="multipart/form-data">
<table border=0 align="center">
<tr>
<td width=150 align="center" bgcolor=#FF9933>
글번호
</td>
<td >
<input type="text" value="${article.articleNO }" disabled />
<input type="hidden" name="articleNO" value="${article.articleNO}" />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
작성자 아이디
</td>
<td >
<input type=text value="${article.id }" name="writer" disabled />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
제목
</td>
<td>
<input type=text value="${article.title }" name="title" id="i_title" disabled />
</td>
</tr>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
내용
</td>
<td>
<textarea rows="20" cols="60" name="content" id="i_content" disabled />${article.content }</textarea>
</td>
</tr>
<c:if test="${not empty article.imageFileName && article.imageFileName!='null' }">
<tr>
<td width="150" align="center" bgcolor="#FF9933" rowspan="2">
이미지
</td>
<td>
<input type= "hidden" name="originalFileName" value="${article.imageFileName }" />
<img src="${contextPath}/download.do?articleNO=${article.articleNO}&imageFileName=${article.imageFileName}" id="preview" /><br>
</td>
</tr>
<tr>
<td>
<input type="file" name="imageFileName " id="i_imageFileName" disabled onchange="readURL(this);" />
</td>
</tr>
</c:if>
<tr>
<td width="150" align="center" bgcolor="#FF9933">
등록일자
</td>
<td>
<input type=text value="<fmt:formatDate value="${article.writeDate}" />" disabled />
</td>
</tr>
<tr id="tr_btn_modify" >
<td colspan="2" align="center" >
<input type=button value="수정반영하기" onClick="fn_modify_article(frmArticle)" >
<input type=button value="취소" onClick="backToList(frmArticle)">
</td>
</tr>
<tr id="tr_btn" >
<td colspan="2" align="center">
<input type=button value="수정하기" onClick="fn_enable(this.form)">
<input type=button value="삭제하기" onClick="fn_remove_article('${contextPath}/board/removeArticle.do', ${article.articleNO})">
<input type=button value="리스트로 돌아가기" onClick="backToList(this.form)">
<input type=button value="답글쓰기" onClick="fn_reply_form('${contextPath}/board/replyForm.do', ${article.articleNO})">
</td>
</tr>
</table>
</form>
</body>
</html>
9. 답글 기능창 replyForm.jsp를 다음과 같이 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
isELIgnored="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<%
request.setCharacterEncoding("UTF-8");
%>
<head>
<meta charset="UTF-8">
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
function backToList(obj){
obj.action="${contextPath}/board/listArticles.do";
obj.submit();
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#preview').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
</script>
<title>답글쓰기 페이지</title>
</head>
<body>
<h1 style="text-align:center">답글쓰기</h1>
<form name="frmReply" method="post" action="${contextPath}/board/addReply.do" enctype="multipart/form-data">
<table align="center">
<tr>
<td align="right"> 글쓴이: </td>
<td><input type="text" size="5" value="lee" disabled /> </td>
</tr>
<tr>
<td align="right">글제목: </td>
<td><input type="text" size="67" maxlength="100" name="title" /></td>
</tr>
<tr>
<td align="right" valign="top"><br>글내용: </td>
<td><textarea name="content" rows="10" cols="65" maxlength="4000"> </textarea> </td>
</tr>
<tr>
<td align="right">이미지파일 첨부: </td>
<td> <input type="file" name="imageFileName" onchange="readURL(this);" /></td>
<td><img id="preview" src="#" width=200 height=200/></td>
</tr>
<tr>
<td align="right"> </td>
<td>
<input type=submit value="답글반영하기" />
<input type=button value="취소"onClick="backToList(this.form)" />
</td>
</tr>
</table>
</form>
</body>
</html>
10. /board/listArticles.do로 요청하여 실행결과를 확인
11. 두번째 페이지를 클릭했을 때의 결과