JSP BBS (Bulletin Board System) Internet Computing Laboratory @ KUT Youn-Hee Han
Design 게시물에 할당해야 할 값 일렬 번호 (테이블 필드명: THEME_MESSAGE_ID) 게시물마다 고유의 값을 가지면 글이 추가될 때 마다 1씩 증가 그룹 번호 (GROUP_ID) 연관된 게시물들이 공유하는 번호 주 게시물 및 그 게시물의 모든 답글은 같은 그룹 번호를 가짐 부모 번호 (PARENT_ID) 주 게시물은 항상 0을 지님 답변 게시물은 해당 주 게시물의 일렬 번호를 부모 번호로 가짐 출력 순서 (ORDER_NO) 같은 그룹 내의 답변 게시물 들에 대한 출력 순서를 저장 단계값 (LEVEL) 주 게시물은 항상 1을 지님 얼마나 중첩이 된 답변 게시물인지를 나타냄 Web Programming
Design 게시물에 할당해야 할 값 Primary Key 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 2 입력 순서 Hi! Hello~~~ 1 2 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 3 2 입력 순서 Hi! Me too! Hello~~~ 1 3 2 Web Programming
Design 게시물에 할당해야 할 값 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 3 2 4 입력 순서 3 2 4 입력 순서 1 Hi! Me too! I’m not! Hello~~~ 4 3 2 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 3 2 5 4 입력 순서 Hi! Me too! Me three! I’m not! Hello~~~ 1 3 5 4 2 Web Programming
Design 게시물에 할당해야 할 값 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 3 2 5 6 4 3 2 5 6 4 입력 순서 1 Hi! Me too! Me three! Good!!! I’m not! Hello~~~ 3 5 6 4 2 일련 번호 그룹 번호 부모 번호 출력 순서 단계값 (들여쓰기) 1 3 2 5 6 4 7 입력 순서 1 Hi! Me too! Me three! Good!!! Me four! I’m not! Hello~~~ 3 5 6 7 4 2 Web Programming
DB Schema for BBS 데이터베이스 Table 목록 THEME_MESSAGE(, NOTICE_MESSAGE, STUDY_MESSAGE…) THEME_CONTENT(, NOTICE_CONTENT, STUDY_CONTENT…) Web Programming
DB Schema for BBS 데이터베이스 Table 목록 인덱스 ID_SEQUENCE THEME_MESSAGE_INDEX Web Programming
컴파일러 준비 sjc.bat 다음과 같이 재정비 할 필요 기본 위치는 java 설치 디렉토리의 bin 디렉토리 밑 하지만, \2006777888\WEB-INF\src 밑에 두고 써도 상관없음 그러나, 시스템 전체에 sjc.bat는 하나만 존재해야 함! set CLASSPATH= set CLASSPATH=%CLASSPATH%;D:\jakarta-tomcat-5.0.19\webapps\2006777888 \WEB-INF\classes;D:\jakarta-tomcat-5.0.19\common\lib\servlet-api.jar javac -d D:\jakarta-tomcat-5.0.19\webapps\2006777888\WEB-INF\classes %1 Web Programming
DB 준비 데이터베이스 준비 MySQL 4.0.18 설치 도스창에서 mysqladmin -p create theme 압축 해제 디렉토리: d:\install_mysql 설치 디렉토리: d:\mysql d:\mysql\bin\winmysqladmin.exe 을 실행한다. User ID는 root로 PASSWD는 적절하게 넣어준다. 도스창에서 mysqladmin -p create theme d:\mysql\bin\ 에서 수행 패스워드는 아무것도 넣지 말고 엔터 누름 theme.sql 다운받기 d:\mysql\bin\ 에 다운받는다. 도스창에서 mysql -p theme < theme.sql 패스워드는 디폴트로 없이 수행 Web Programming
WEB Application 준비 kut.ime.jdbcdriver.DBCPInit 서블릿 클래스 DBCPInit.java 다운로드 다운로드 디렉토리: \2006777888\WEB-INF\src 패키지명 유의 컴파일 Web Programming
Connection Pool 및 JDBC 드라이버 로드 pool.jocl 다운로드: /2006777888/WEB-INF/classes 밑에 저장 교재 474 페이지 참고 JDBC Driver 사용을 위한 web.xml 수정 교재 475 페이지 참고 web.xml 내에 다음을 추가 … <servlet> <servlet-name>DBCPInit</servlet-name> <servlet-class>kut.ime.jdbcdriver.DBCPInit</servlet-class> <load-on-startup>1</load-on-startup> <init-param> <param-name>jdbcdriver</param-name> <param-value>com.mysql.jdbc.Driver</param-value> </init-param> </servlet> Web Programming
Connection Pool 및 JDBC 드라이버 로드 다운로드 위치: /2006777888/WEB-INF/lib 다운로드할 파일 classes12.jar commons-collections-3.1.jar commons-dbcp-1.2.1.jar commons-pool-1.2.jar mysql-connector-java-3.0.14-production-bin.jar Tomcat 재시작 DB 테스트 2006777888/bbs/test.jsp <%@ page import="java.sql.*"%> <% Connection conn = DriverManager.getConnection("jdbc:apache:commons:dbcp:/"+"pool"); ResultSet rs = conn.createStatement().executeQuery("show tables;"); while(rs.next()) { out.println(rs.getString(1) + "<BR>"); } %> pool.jocl Web Programming
보조 클래스 kut.ime.util.DBUtil 클래스 2006777888/WEB-INF/src/DBUtil.java package kut.ime.util; import java.sql.DriverManager; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { public static Connection getConnection(String poolName) throws SQLException { return DriverManager.getConnection( "jdbc:apache:commons:dbcp:/"+poolName); } 이후 모든 .java 소스는 다운 받아서 sjc 로 컴파일 필요
보조 클래스 kut.ime.bbs.Sequencer 클래스 (교재 p.476) 다음의 1개 메소드 제공 public synchronized static int nextID(Connection conn, String tableName) ID_SEQUENCE 테이블에 저장된 게시물들의 가장 큰 일련 번호인 MESSAGE_ID 필드 값을 얻어와 하나 증가된 값을 리턴해줌 사용법 int id = Sequencer.nextId(conn, “THEME_MESSAGE”); kut.ime.bbs.Theme 클래스 (교재 p.477~479) 자바빈 클래스 THEME_MESSAGE 테이블과 THEME_CONTENT 테이블의 내용을 모두 포함 용도 새로운 게시물을 입력할 때 게시물을 얻어올 때 게시물을 업데이트 할 때 kut.ime.bbs.ThemeManagerException 클래스 (교재 p.480) 본 JSP BBS 관련 작업 처리 중 발생하는 예외를 다른 예외들과 구별하기 위하여 필요함
주 클래스: ThemeManager ThemeManager 클래스 JSP BBS의 대부분의 기능을 수행하는 클래스 메소드 목록 public static ThemeManager getInstance(); ThemeManager의 인스턴스를 얻어옴 public void insert(Theme theme) throws ThemeManagerException; 게시판에 새로운 글을 삽입할 때 사용 public int count(List whereCond, Map valueMap) throws ThemeManagerException; 특정 조건에 만족하는 게시물의 총 개수를 구함 public List selectList(List whereCond, Map valueMap, int startRow, int endRow) throws ThemeManagerException; 특정 조건에 만족하는 게시물을 목록을 가져 올때 사용 public int select(int id) throws ThemeManagerException; 하나의 게시물을 읽어 올 때 사용 public int update(Theme theme) throws ThemeManagerException; 게시물 하나를 업데이트 할 때 사용 public int delete(int id) throws ThemeManagerException; 게시물 하나를 삭제 할 때 사용
클래스 배치 현재까지 클래스 컴파일 결과 모습
이미지 업로드&핸들링을 대비한 사전 준비 작업 폴더 생성 업로드 이미지 임시 저장소 D:\jakarta-tomcat-5.0.19\webapps\2006777888\bbs\temp 업로드 이미지 저장소 D:\jakarta-tomcat-5.0.19\webapps\2006777888\bbs\image ImageUtil 클래스 배치 FileUploadRequestWrapper 클래스 배치
이미지 업로드&핸들링을 대비한 사전 준비 작업 라이브러리 재확인 TOMCAT 재시작!!!
화면 출력용 JSP 구성 BBS.zip 다운로드 화면 레이아웃과 모듈화 다운로드 위치: 2006777888/bbs/ write.jsp 에서 2006777888을 자신의 폴더 이름으로 변경 화면 레이아웃과 모듈화 교재 494 페이지 참조 2006777888/bbs/template/template.jsp 2006777888/bbs/module/top.jsp 2006777888/bbs/list_view.jsp 2006777888/bbs/writeForm_view.jsp 2006777888/bbs/read_view.jsp 2006777888/bbs/updateForm_view.jsp 2006777888/bbs/deleteForm_view.jsp 2006777888/bbs/module/bottom.jsp
화면 출력용 JSP 구성 JSP 구성 및 연결도 (아래 파일들은 2006777888/bbs 아래에 위치함) 게시물 목록 출력 새 게시물 입력 /module/top.jsp list.jsp /template/template.jsp list_view.jsp /module/bottom.jsp /module/top.jsp writeForm.jsp /template/template.jsp writeForm_view.jsp /module/bottom.jsp form action javascript location.href = “list.jsp” write.jsp
화면 출력용 JSP 구성 JSP 구성 및 연결도 /module/top.jsp read.jsp 게시물 읽기 게시물 수정 /module/top.jsp read.jsp /template/template.jsp read_view.jsp /module/bottom.jsp /module/top.jsp updateForm.jsp /template/template.jsp updateForm_view.jsp /module/bottom.jsp form action javascript document.move.action = "list.jsp"; document.move.submit(); list.jsp update.jsp
화면 출력용 JSP 구성 JSP 구성 및 연결도 /module/top.jsp deleteForm.jsp 게시물 삭제 /module/top.jsp deleteForm.jsp /template/template.jsp deleteForm_view.jsp /module/bottom.jsp form action javascript location.href = “list.jsp” list.jsp delete.jsp
BBS 수행 모습 수행 모습 데이터 베이스 모습
게시물 목록 출력 코드 게시물 목록 출력 코드 설명 (검색 조건 없을 경우) 관련 코드 리스트 출력할 페이지 번호 설정 코드 리스트 18.18, 18.8, 18.9 출력할 페이지 번호 설정 currentPage 변수 설정 searchCond, searchKey 는 null 이라고 가정 따라서, whereCond, whereValue, searchCondName, searchCondTitle 모두 null 글의 전체 개수를 얻어옴 int count = manager.count(whereCond, whereValue); 수행되는 Query 문 select count(*) from THEME_MESSAGE 전체 페이지 개수와 읽어 올 시작행, 끝행을 구한다. totalPageCount, startRow, endRow 설정
게시물 목록 출력 코드 게시물 목록 출력 코드 설명 (검색 조건 없을 경우) – 계속 글 목록을 읽어 옴 List list = manager.selectList(whereCond, whereValue, startRow-1, endRow-1); 수행되는 Query 문 select * from THEME_MESSAGE order by GROUP_ID desc, ORDER_NO asc limit ?, ? Java 코딩에서 Query 구성 법 EL과 JSTL을 이용해서 글 목록 출력 글 목록 및 하나의 게시글 보기에 대한 링크 생성 자바 스크립트와 form을 활용 페이지 이동 링크 출력 ([이전], [1], [2], …., [다음]) 검색 폼 출력 리스트 18.10 StringBuffer query = new StringBuffer(200); query.append(“select * from THEME_MESSAGE “); query.append(“order by GROUP_ID desc, ORDER_NO asc limit ?, ?”); Connection conn = DBUtil.getConnection(POOLNAME); PreparedStatement pstmtMessage = conn.prepareStatement(query.toString()); pstmtMessage.setInt(valueMap.size() + 1, startRow); pstmtMessage.setInt(valueMap.size() + 2, endRow- startRow + 1); ResultSet rsMessage = pstmtMessage.executeQuery();
게시물 목록 출력 코드 게시물 목록 출력 코드 설명 (검색 조건 있을 경우) 관련 변수 선언 검색 조건 구성 value: title 게시물 목록 출력 코드 설명 (검색 조건 있을 경우) 관련 변수 선언 검색 조건 구성 value: name name: search_cond name: search_key 리스트 18.8 String[] searchCond = request.getParameterValues("search_cond"); String searchKey = request.getParameter("search_key"); boolean searchCondName = false; boolean searchCondTitle = false; List whereCond = new java.util.ArrayList(); Map whereValue = new java.util.HashMap(); 리스트 18.8 for (int i = 0 ; i < searchCond.length ; i++) { if (searchCond[i].equals("name")) { whereCond.add("NAME = ?"); whereValue.put(new Integer(1), searchKey); searchCondName = true; } else if (searchCond[i].equals("title")) { whereCond.add("TITLE LIKE '%"+searchKey+"%'"); searchCondTitle = true; } whereCond 구성 예 ( List) : < “NAME=?”, “TITLE LIKE ‘%공지%’ ”> whereValue 구성 예 ( Map) : < (new Integer(1), ”한연희”), >
게시물 목록 출력 코드 게시물 목록 출력 코드 설명 (검색 조건 있을 경우) – 계속 글의 전체 개수를 얻어옴 int count = manager.count(whereCond, whereValue); 수행되는 Query 문 select count(*) from THEME_MESSAGE where NAME=한연희 select count(*) from THEME_MESSAGE where NAME=한연희 or title like ‘%공지%’ Java 코딩에서 Query 구성 법 리스트 18.9 StringBuffer query = new StringBuffer(200); query.append("select count(*) from THEME_MESSAGE "); if (whereCond != null && whereCond.size() > 0) { query.append("where "); for (int i = 0 ; i < whereCond.size() ; i++) { query.append(whereCond.get(i)); if (i < whereCond.size() -1 ) { query.append(" or "); } pstmt = conn.prepareStatement(query.toString()); Iterator keyIter = valueMap.keySet().iterator(); Integer key = (Integer)keyIter.next(); Object obj = valueMap.get(key); pstmt.setString(key.intValue(), (String)obj);
게시물 목록 출력 코드 게시물 목록 출력 코드 설명 (검색 조건 있을 경우) – 계속 새 게시물 입력 링크 글 목록을 읽어 옴 List list = manager.selectList(whereCond, whereValue, startRow-1, endRow-1); 수행되는 Query 문 select * from THEME_MESSAGE where NAME=한연희 or title like ‘%공지%’ order by GROUP_ID desc, ORDER_NO asc limit ?, ? Java 코딩에서 Query 구성 법 글 전체 개수를 구하는 코드에서 Query 구성법과 비슷 검색 조건 없을 때의 Query 구성법과 병합하여 최종 Query를 구성!!! 새 게시물 입력 링크 관련 코드 <td colspan="4" align="right"><a href="writeForm.jsp">[이미지등록]</a></td>
게시물 목록 출력 코드 파라미터 값 갖는 링크 구성 링크 구성 시 활용하는 폼 게시물 링크 리스트 18.8 <form name="move" method="post"> <input type="hidden" name="id" value=""> <input type="hidden" name="page" value="${currentPage}"> <c:if test="<%= searchCondTitle %>"> <input type="hidden" name="search_cond" value="title"> </c:if> <c:if test="<%= searchCondName %>"> <input type="hidden" name="search_cond" value="name"> <c:if test="${! empty param.search_key}"> <input type="hidden" name="search_key" value="${param.search_key}"> </form> 리스트 18.8 <a href="javascript:goView(${theme.id})">${theme.title}</a> . <script language=“JavaScript”> function goView(id) { document.move.action = "read.jsp"; document.move.id.value = id; document.move.submit(); } </script>
게시물 목록 출력 코드 파라미터 값 갖는 링크 구성 페이지 번호 링크 리스트 18.8 <c:set var="count" value="<%= Integer.toString(count) %>" /> <c:set var="PAGE_SIZE" value="<%= Integer.toString(PAGE_SIZE) %>" /> <c:set var="currentPage" value="<%= Integer.toString(currentPage) %>" /> <c:if test="${count > 0}"> <c:set var="pageCount" value="${count / PAGE_SIZE + (count % PAGE_SIZE == 0 ? 0 : 1)}" /> <c:set var="startPage" value="${currentPage - (currentPage % 10) + 1}" /> <c:set var="endPage" value="${startPage + 10}" /> <c:if test="${endPage > pageCount}"> <c:set var="endPage" value="${pageCount}" /> </c:if> <c:if test="${startPage > 10}"> <a href="javascript:goPage(${startPage - 10})">[이전]</a> <c:forEach var="pageNo" begin="${startPage}" end="${endPage}"> <c:if test="${currentPage == pageNo}"><b></c:if> <a href="javascript:goPage(${pageNo})">[${pageNo}]</a> <c:if test="${currentPage == pageNo}"></b></c:if> </c:forEach> <c:if test="${endPage < pageCount}"> <a href="javascript:goPage(${startPage + 10})">[다음]</a> <script language=“JavaScript”> function goPage(pageNo) { document.move.action = "list.jsp"; document.move.page.value = pageNo; document.move.submit(); } </script>
게시물 보기 코드 게시물 보기 코드 설명 게시물 읽어 오기 사용하는 SQL Query 문 Theme 자바빈 구성 select * from THEME_MESSAGE where THEME_MESSAGE_ID = ? select CONTENT from THEME_CONTENT where THEME_MESSAGE_ID = ? Theme 자바빈 구성 리스트 18.23 String themeId = request.getParameter("id"); ThemeManager manager = ThemeManager.getInstance(); Theme theme = manager.select(Integer.parseInt(themeId)); Theme theme = new Theme(); theme.setId(rsMessage.getInt("THEME_MESSAGE_ID")); theme.setGroupId(rsMessage.getInt("GROUP_ID")); . theme.setContent(buffer.toString()); return theme;
게시물 보기 코드 게시물 보기 코드 설명 링크 구성 시 활용하는 폼 리스트 18.23 <form name="move" method="post"> <input type="hidden" name="id" value=“${theme.id}"> <input type="hidden" name=“parentId" value=“${theme.id}"> <input type="hidden" name=“groupId" value=“${theme.groupId}"> <input type="hidden" name="page" value="${param.page}"> <c:forEach var="searchCond" items="${paramValues.search_cond}"> <c:if test="${searchCond == 'title'}"> <input type="hidden" name="search_cond" value="title"> </c:if> <c:if test="${searchCond == 'name'}"> <input type="hidden" name="search_cond" value="name"> </c:forEach> <c:if test="${! empty param.search_key}"> <input type="hidden" name="search_key" value="${param.search_key}"> </form>
게시물 보기 코드 게시물 보기 코드 설명 다양한 링크 구성 <tr> <td colspan="2"> 리스트 18.23 <tr> <td colspan="2"> <a href="javascript:goReply()">[답변]</a> <a href="javascript:goModify()">[수정]</a> <a href="javascript:goDelete()">[삭제]</a> <a href="javascript:goList()">[목록]</a> </td> </tr> <script language="JavaScript"> function goReply() { document.move.action = "writeForm.jsp"; document.move.submit(); } function goModify() { document.move.action = "updateForm.jsp"; function goDelete() { document.move.action = "deleteForm.jsp"; function goList() { document.move.action = "list.jsp"; function viewLarge(imgUrl) { </script>
게시물 입력 코드 게시물 입력 코드 설명 writeForm_view.jsp write.jsp 답변글일 경우 parentId 및 부모글의 제목 읽음 부모글은 theme 변수에 저장 폼 입력시 hidden 필드 구성 폼 입력 내용에 대한 validation 리스트 18.20 <input type="hidden" name="level" value="${theme.level + 1}"> <c:if test="${! empty param.groupId}"> <input type="hidden" name="groupId" value="${param.groupId}"> </c:if> <c:if test="${! empty param.parentId}"> <input type="hidden" name="parentId" value="${param.parentId}"> 리스트 18.20 <form action="write.jsp" method="post" enctype="multipart/form-data" onSubmit="return validate(this)">
게시물 입력 코드 게시물 입력 코드 설명 writeForm_view.jsp이 전달해준 입력 폼 내용을 write.jsp에서 받기 업로드 이미지 저장 및 썸네일 이미지 저장 theme 빈 내용 추가 및 DB 저장 리스트 18.21 <jsp:useBean id="theme" class="kut.ime.bbs.Theme"> <jsp:setProperty name="theme" property="*" /> </jsp:useBean> 리스트 18.21 theme.setRegister(new Timestamp(System.currentTimeMillis())); theme.setImage(image); ThemeManager manager = ThemeManager.getInstance(); manager.insert(theme);
게시물 입력 코드 게시물 입력 코드 설명 답변글이 아닌 경우 제일 큰 그룹번호를 구하는 SQL Query select max(GROUP_ID) from THEME_MESSAGE 답변글인 경우 같은 부모를 지닌 답글 중 제일 큰 순서 번호를 구하는 SQL Query select max(ORDER_NO) from THEME_MESSAGE where PARENT_ID = [theme.getParentId()] or THEME_MESSAGE_ID = [theme.getParentId()] 답변글을 저장할 경우 새롭게 저장될 순서 번호에 맞추어 기존글들의 순서 번호 갱신하는 SQL Query update THEME_MESSAGE set ORDER_NO = ORDER_NO + 1 where GROUP_ID = [theme.getGroupId()] and ORDER_NO >= [theme.getOrderNo()] 새로운 글의 일련 번호를 구함 새로운 글을 삽입하는 SQL Query insert into THEME_MESSAGE values (?,?,?,?,?,?,?,?,?,?,?)"); insert into THEME_CONTENT values (?,?) 리스트 18.8 theme.setId(Sequencer.nextId(conn, "THEME_MESSAGE"));