댓글게시판 : 댓글 작성 기능 (이어서)

현재 작성된 글과 변수 상태

- 목록페이지에서 목록을 보여줄때 ref 컬럼으로 내림차순 정렬, re_step 컬럼으로 오름차순 정렬

- ref 컬럼으로 정렬시 최근글이 위로 간다, 그리고 부모글과 자식글(댓글)은 ref 값이 같다

- 그러므로 re_step 컬럼으로 오름차순 정렬을 한다

 

만약

- '다섯번째 원문'을 선택하고 '네번째 댓글' 을 달면 다섯번째 원문에 달린 댓글과 대댓글들의 re_step 값이 모두 1씩 증가

- 'Re.두번째 댓글' 을 선택하고 'Re.3대댓글'을 달면 부모의 re_step 인 2 보다 큰 re_step 값을 가진 3,4,5 를 re_step 으로 가진 댓글의 re_step 을 1씩 증가하고 자신이 3이 됨

 

reply() 메소드 SQL문 정리

- 원문이 부모일때, 댓글이 부모일떄를 나눠서 생각해야한다

 

 

* 주의 : 작성 순서를 자세히 보기

* 먼저 달린 댓글 = re_step 값이 크다, 즉 부모보다 큰 re_step 값을 가진 댓글은 부모보다 먼저 달린 댓글임

 

1. 원문이 부모일때

1-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 원문글에 달려있던 댓글들 지칭

- 조건 b. 're_step > 부모의 re_step' 의미 = 부모의 re_step 은 0 이므로 그 원문의 모든 댓글 지칭

- 조건 a & b. 그 부모의 모든 댓글을 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

1-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 1 로 설정

- re_step 은 부모보다 +1 을 해서 1 로 설정, 기존의 댓글들이 모두 update 문에 의해 re_step값이 +1 되어서 2부터 시작하므로 새 댓글의 re_step 값을 1로 설정할 수 있다

 

2. 원문이 댓글일때

2-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 댓글의 원문 + 그 원문의 모든 댓글들 + 그 원문의 대댓글들 (즉 그림에서 빨간색으로 묶은 모든 글)

- 조건 b. 're_step > 부모의 re_step' 의미 = 그 부모 댓글과 형제인 댓글 중 부모 댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들

- 조건 a & b. 그 부모댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

2-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 2 로 설정

- re_step 은 부모보다 +1 을 해서 설정, 그러므로 부모 바로 밑에 출력된다, 부모댓글보다 먼저 작성되었던 댓글과 그 댓글의 대댓글들이 모두 update 문에 의해 re_step 값이 +1 이 되어서 비어있는 숫자를 가지는 것

+ 그림처럼 정렬하기 위해서 이 작업을 해야한다, 단순 작성 최신순이 아님, re_step 기준으로 오름차순 정렬이다


+ 원문 글 작성양식과 댓글 작성 양식이 따로 되어있어야만 시퀀스를 사용 가능하다
+ 댓글을 작성할때 num, page, 부모글에 대한 3가지 정보 (ref, re_step, re_level) 의 변수값을 넘겼다

- 목록페이지 list.jsp 로 부터 시작해서 값이 계속 넘어가고 있는 것을 URL 창에서 확인 가능


 

댓글게시판 : 글 수정

- 수정 폼부터 만들어야 한다

- 상세페이지 content.jsp 에서 버튼 4개 중 '수정' 버튼에 수정폼인 updateForm.jsp 를 연결

+ content.jsp 전체 코드는 삭제 구현 이후에 올리기

- 목록에서 상세페이지로 넘어갈때 전달되었던 num, nowpage 값을 다시 상세페이지에서 수정폼으로 전달한다

- 이후 updateForm.jsp 를 생성 및 작성하자

 

- updateForm.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>


<html>
<head>
<title>글수정</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<body bgcolor="<%=bodyback_c%>">  
<center><b>글수정</b>
<br>
<form method="post"  action="updatePro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="page" value="<%=nowpage%>">
<input type="hidden" name="ref" value="<%=board.getRef()%>">
<input type="hidden" name="re_step" value="<%=board.getRe_step()%>">
<input type="hidden" name="re_level" value="<%=board.getRe_level()%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" 
       name="writer" value="<%=board.getWriter() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">
       <input type="text" size="40" maxlength="50" id="subject" 
       name="subject" value="<%=board.getSubject() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" 
       name="email" value="<%=board.getEmail() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"><%=board.getContent() %></textarea> </td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글수정" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
    
</form>      
</body>
</html>

- writeForm.jsp 의 내용을 복붙 후 수정

- 먼저 상세페이지 content.jsp 에서 넘어간 num, nowpage 의 값을 받아주는 내용을 추가함

- 글 수정시, 글의 정보를 보여줘야하므로 DB와 연동해서 글 1개의 상세정보를 가져와야한다

- updateContent()와 달리 getContent() 에서는 조회수 증가시키는 작업을 하지 않음

- getContent()로 받아온 상세정보에서 ref, re_step, re_level 값을 getter 메소드로 가져와서 hidden 으로 updatePro.jsp로 넘김

- 즉, 5가지 정보인 num, nowpage, re_step, re_level, re_step 값을 form의 hidden 을 통해 updatePro.jsp 로 넘긴다

- 또한 수정 폼에 입력한 5개의 정보들도 form 을 통해 updatePro.jsp 로 넘긴다

- 입력양식에 받아온 상세정보들을 value 속성으로 출력함, 다만 textarea 는 value 속성이 아닌 태그 사이에 작성해야함

- list.jsp 로 넘어가는 링크들에 ?page=<%=nowpage%> 를 추가해서 원래 페이지로 돌아가도록 하기

 

- DAO 클래스로 가서 getContent() 메소드를 추가하자

 

DAO 클래스 글 상세 정보 구하기 메소드 작성

- BoardDBBean.java 전체 코드는 완성 후 올리기 (검색어 : BoardDBBean.java 전체 코드 )

- 추가한 코드인 getContent() 메소드 부분 코드만

	// 수정 폼 : 데이터 1개 추출
	public BoardDataBean getContent(int num) {
		
		BoardDataBean board = new BoardDataBean();	
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return board;
	}

 

- 글 1개에 대한 상세정보를 구해줘야하므로 리턴자료형은 DTO 이다

- 글 번호인 num 으로 상세 정보를 구해와야하므로 매개변수는 int 형

- updateContent() 메소드 안의 내용을 통째로 복붙 후, 조회수 증가시키는 코드 부분만 지우기

- 다음으로 updatePro.jsp 파일을 생성 및 작성하자

- updatePro.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="board" class="reboard.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	board.setIp(request.getRemoteAddr());
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시
		int result = dao.update(board);	// update SQL문 실행
		
		if(result == 1) {
%>
			<script>
				alert("글 수정 성공");
				location.href = "list.jsp?page=<%=nowpage%>";
			</script>
<%		
		}
	} else {	// 비번 불일치시
%>
			<script>
				alert("비번이 일치하지 않습니다.");
				history.go(-1);
			</script>
<%
		
	}
%>

- 폼에서 한글값이 post 방식으로 넘어오므로 한글 인코딩

- useBean, setProperty action tag 를 사용해서 폼에서 넘어온 값을 DTO 객체에 저장

- 이때, 폼에서 ip 주소값은 전달되지 않았고, DTO 프로퍼티중 page 값을 저장할 곳이 없으므로 따로 처리

- DB에서 getContent() 메소드로 비번을 끄집어 내서 수정폼에서 사용자가 입력한 비번과 일치하는지 확인

- getContent() 매개변수에는 글의 번호인 num 이 들어가므로 수정폼에서 넘어온 값들을 저장한 board 객체에서 getNum() 으로 num 값을 꺼내서 매개변수로 넣기

- DB의 비밀번호인 old.getPasswd() 와 사용자가 수정폼에서 입력한 비밀번호인 getPasswd() 가 일치하면 수정 메소드인 update() 호출

 

- DAO 클래스로 가서 update() 메소드를 추가하자

 

DAO 클래스 글 수정 메소드 작성

- BoardDBBean.java 전체 코드는 완성 후 올리기 (검색어 : BoardDBBean.java 전체 코드 )

- 추가한 코드인 update() 메소드 부분 코드만

	// 글 수정
	public int update(BoardDataBean board) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return result;
	}

- update 된 데이터 개수를 반환하기 위해 리턴자료형은 int

- 사용자가 수정폼에서 입력한 값을 전달받아야하므로 매개변수 자료형은 DTO

- 수정폼에서 넘어온 데이터를 저장하는 board 객체에서 값을 가져와서 update 문을 실행

 

- list.jsp 를 실행 후 글 '다섯번째 원문' 을 선택하여 목록페이지->상세페이지-> 수정폼 에서 글 수정을 시도해보자

- 수정 완료

 

댓글게시판 : 글 삭제

- 삭제 폼부터 만들어야 한다

- 상세페이지 content.jsp 에서 버튼 4개 중 '삭제' 버튼에 수정폼인 deleteForm.jsp 를 연결

 

- content.jsp (최종)

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 조회수 1 증가 + 상세 정보 구하기
	BoardDataBean board = dao.updateContent(num);
	
	// 부모글 정보 구하기
	int ref = board.getRef();
	int re_level = board.getRe_level();
	int re_step = board.getRe_step();
	
	SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	String content = board.getContent().replace("\n","<br>");
%>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=400 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>번호</td>
		<td><%=board.getNum() %></td>
		<td>조회수</td>
		<td><%=board.getReadcount() %></td>
	</tr>
	<tr>
		<td>작성자</td>
		<td><%=board.getWriter() %></td>
		<td>작성일</td>
		<td><%=sd.format(board.getReg_date()) %></td>
	</tr>
	<tr>
		<td>제목</td>
		<td colspan=3><%=board.getSubject() %></td>
	</tr>
	<tr>
		<td>내용</td>
		<td colspan=3>
 			<pre><%=board.getContent() %></pre>
			<%=content %>
		</td>
	</tr>
	<tr>
		<td colspan=4 align=center>
			<input type="button" value="댓글"
			onClick="location.href='replyForm.jsp?num=<%=num%>&page=<%=nowpage%>&ref=<%=ref%>&re_level=<%=re_level%>&re_step=<%=re_step%>'">
			
			<input type="button" value="수정"
			onClick="location.href='updateForm.jsp?num=<%=num %>&page=<%=nowpage %>'">
			
			<input type="button" value="삭제"
			onClick="location.href='deleteForm.jsp?num=<%=num %>&page=<%=nowpage %>'">
			
			<input type="button" value="목록" 
			onClick="location.href='list.jsp?page=<%=nowpage%>'">
		</td>
	</tr>
</table>
</body>
</html>

 

- 삭제 폼인 deleteForm.jsp 파일부터 먼저 생성 후 작성

- deleteForm.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>


<html>
<head>
<title>글삭제</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<body bgcolor="<%=bodyback_c%>">  
<center><b>글삭제</b>
<br>
<form method="post"  action="deletePro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="page" value="<%=nowpage%>">
<input type="hidden" name="ref" value="<%=board.getRef()%>">
<input type="hidden" name="re_step" value="<%=board.getRe_step()%>">
<input type="hidden" name="re_level" value="<%=board.getRe_level()%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글삭제" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
    
</form>      
</body>
</html>

- updateForm.jsp 파일을 복붙 후 비번 입력 양식 제외 입력 양식 삭제

- DB연동 코드와 ref, re_step, re_level 값을 hidden 으로 넘기는 부분은 지워도 되지만 여기선 놔둠

- form 을 통해 비밀번호가 deletePro.jsp 로 전달됨

 

- deletePro.jsp 생성 및 작성

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="board" class="reboard.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	board.setIp(request.getRemoteAddr());
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시
		int result = dao.delete(board);	// update SQL문 실행
		
		if(result == 1) {
%>
			<script>
				alert("글 삭제 성공");
				location.href = "list.jsp?page=<%=nowpage%>";
			</script>
<%		
		}
	} else {	// 비번 불일치시
%>
			<script>
				alert("비번이 일치하지 않습니다.");
				history.go(-1);
			</script>
<%
	}
%>

- updatePro.jsp 파일 내용을 복붙 후 수정

- ip 주소값을 DTO 객체에 저장하는 코드는 필요없으므로 삭제

- 비번 일치시 update() 메소드 대신 delete() 메소드 호출

 

삭제시 주의할 점

- 원문을 삭제시 그 아래의 자식 댓글들만 나오게 된다, 보기 좋지 않음

- 일반적으로, 원문을 삭제 시도시 실제로 삭제하진 않고 제목에 삭제되었다는 메세지를 출력하고 링크를 없앤다

- 원문은 re_level 이 0 이거나 re_step 이 0 이므로 원문 구별 가능

 

- DAO 클래스에서 delete() 메소드를 작성하자


DAO 클래스 글 삭제 메소드 작성

- BoardDBBean.java 전체 코드

// DAO (Data Access Object)
package reboard;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean getInstance() {	// 정적 메소드
		return instance;
	}
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	public Connection getConnection() throws Exception {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 원문 글작성
	public int insert(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,board_seq.nextval,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0);		// readcount
			pstmt.setInt(6, 0);		// re_step
			pstmt.setInt(7, 0);		// re_level
			pstmt.setString(8, board.getContent());
			pstmt.setString(9, board.getIp());
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		
		return result;
	}
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select count(*) from board";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			if(rs.next()) {
//				result = rs.getInt(1); // 아래와 같다
				result = rs.getInt("count(*)");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}
	
	// 데이터 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += "(select * from board order by ref desc, re_step asc) board) ";
			sql += "where rnum >= ? and rnum <= ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, start);
			pstmt.setInt(2, end);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			while(rs.next()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				list.add(board);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}			
		}
		return list;
	}
	
	// 상세 페이지 : 조회수 1 증가 시키고 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// SQL문 실행
			
			sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return board;
	}
	
	// 댓글 작성
	public int reply(BoardDataBean board) {
		int result = 0;
		
		// 부모글에 대한 정보
		int ref = board.getRef();
		int re_step = board.getRe_step();
		int re_level = board.getRe_level();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
// 1. 원문이 부모인 경우
//		: 원문이 re_step=0 이기 떄문에, 모든 댓글들의 re_step 값이 1 증가 된다.
// 2. 댓글이 부모인 경우
//		: 부모의 re_step 보다 큰 댓글만 re_step 값이 1 증가 된다.
			
			String sql = "update board set re_step=re_step+1 ";
			sql += "where ref=? and re_step > ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, ref);		// 부모의 ref
			pstmt.setInt(2, re_step); 	// 부모의 re_step
			
			pstmt.executeUpdate();		// SQL문 실행
			
			sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,?,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0); 			// readcount
			pstmt.setInt(6, ref); 			// ref
			pstmt.setInt(7, re_step+1); 	// re_step
			pstmt.setInt(8, re_level+1); 	// re_level
			pstmt.setString(9, board.getContent());
			pstmt.setString(10, board.getIp());
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}							
		}
			
		return result;
	}
	
	// 수정 폼 : 데이터 1개 추출
	public BoardDataBean getContent(int num) {
		
		BoardDataBean board = new BoardDataBean();	
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return board;
	}
	
	// 글 수정
	public int update(BoardDataBean board) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return result;
	}
	
	// 글 삭제
	public int delete(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		String sql = "";
		
		try {
			con = getConnection();
			
			if(board.getRe_level() == 0) {	// 원문
				sql = "update board set subject=?,content=? where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setString(1, "관리자에 의해서 삭제됨");
				pstmt.setString(2, " ");
				pstmt.setInt(3, board.getNum());
			} else {						// 댓글
				sql = "delete from board where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setInt(1, board.getNum());
			}
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return result;
	}
}

 

- 추가한 코드인 delete() 메소드 부분 코드만

	// 글 삭제
	public int delete(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		String sql = "";
		
		try {
			con = getConnection();
			
			if(board.getRe_level() == 0) {	// 원문
				sql = "update board set subject=?,content=? where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setString(1, "관리자에 의해서 삭제됨");
				pstmt.setString(2, "");
				pstmt.setInt(3, board.getNum());
			} else {						// 댓글
				sql = "delete from board where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setInt(1, board.getNum());
			}
			
			result = pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}					
		}
		return result;
	}

- if문을 이용해서 원문인지 아닌지 re_level 을 가져와서 판별

- 원문인 경우 해당 원문글의 제목을 "관리자에 의해 삭제됨" 으로 수정하고 내용을 ""으로 수정함

- 댓글인 경우 그냥 delete문으로 바로 삭제

 

+ 매개변수에 board 안의 num 변수만 넘겨도 가능하지만 여기선 DTO 객체를 통째로 넘겼다

 

- 댓글을 삭제할때

- list.jsp 를 실행 후 댓글 'Re1.대댓글' 을 선택하여 목록페이지->상세페이지-> 삭제폼 에서 글 삭제를 시도해보자

 

- 원문을 삭제할때

- list.jsp 를 실행 후 댓글 '다섯번째 원문(수정)' 을 선택하여 목록페이지->상세페이지-> 삭제폼 에서 글 삭제를 시도해보자

 

+ 삭제된 글인경우 목록페이지에서 링크를 없애고 싶다면

- 테이블에 특정 컬럼 del 이라는 varchar2 컬럼을 추가하고 insert 당시에 그 컬럼값을 'n' 으로 한다

- 글을 삭제했을때 컬럼값을 'y' 로 설정함

- 그럼 삭제를 한 글인지 판별하는 컬럼이 생긴다

- 그 컬럼을 통해서 삭제된 글은 링크를 달지 않기

 

댓글게시판 : 초기 페이지

- index.jsp 파일을 작성하자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
댓글 게시판

<script>
	location.href = "reboard/list.jsp";
</script>

</body>
</html>

- 가장 먼저 자동으로 실행되는 파일인 index.jsp 파일에 reboard 폴더 안의 list.jsp 가 실행되도록 함

 

- 프로젝트 처음부터 실행하는 방법

- 프로젝트를 클릭후 Run on Server 로 실행시켰을때 index.jsp 부터 자동으로 실행된다

 

delete 원문삭제 가 안됨
update /delete 성공 후 list.jsp 로 돌아가는게 안됨


자료실 게시판 소개

- 첨부파일을 업로드 / 다운로드 가능한게 자료실 기능을 가진 게시판

- 업로드 / 다운로드 하는 방법부터 먼저 학습 후 자료실 기능 게시판 만들 것

 

첨부파일 저장

- 첨부파일은 일반 값과는 달리 테이블에 저장되지 않고 서버측에 지정된 디렉토리 안에 저장된다

- 첨부파일명만 테이블에 저장된다

- 지정한 upload 디렉토리안에 첨부파일이 저장됨

 

첨부파일 업로드 / 다운로드 라이브러리

- 첨부파일 업로드 / 다운로드를 위해서 따로 라이브러리가 필요하다

- 여러 라이브러리 중 cos.jar 라는 라이브러리를 사용해서 첨부파일을 업로드 / 다운로드 할 것

- cos.jar 라이브러리를 프로젝트의 lib 폴더 안에 저장해야한다

 

+ cos 라이브러리 장점

- Model 1, Model 2 에서 많이 사용함

- upload 디렉토리 안에 첨부파일을 저장할때 동일 폴더이므로 파일명 중복문제가 발생시 자동으로 처리해줌

ex) A 가 자료실에 파일명을 kuro.jpg 로 올리고, B도 자료실에 파일명을 kuro.jpg 로 올렸다면 중복 발생

- 자동으로 번호값을 부여해서 중복 문제를 해결함

ex) kuro.jpg, kuro1.jpg, kuro2.jpg 처럼 끝에 숫자를 붙여줌

- 다른 라이브러리는 같은 이름의 파일이 있는지 없는지 검사하며 개발자가 중복 문제를 직접 처리해야함

 

cos 라이브러리 다운

- zip 파일을 다운

- 다운로드 후 압축 풀기

+ doc 폴더 안에는 API 문서가 있다

+ doc폴더 안에서 index 파일 실행시 API 문서를 볼 수 있음

 

 

첨부파일 업로드 / 다운로드

실습 준비 / 환경 구축

- 업로드를 수행하기 위한 예제 파일들이 있는 uploadTest 를 jspproject 프로젝트 로 가져오기

- 이후 WebContent 폴더 하위에 업로드를 하기 위한 폴더인 upload 폴더 생성

- 아까 다운받은 cos-22.05 중 lib 폴더 안의 cos.jar 파일을 이클립스의 jspproject 프로젝트 WEB-INF/lib 폴더 안에 넣기

 

첨부파일 서버로 전송하기

- 반드시 post 방식만 가능하다

- form 태그안에 속성으로 이 코드가 있어야한다

 

- uploadTest 폴더 안의 fileUploadForm.jsp 파일 열기

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>FileUpload Form</title>
</head>
<body>
<center>
<form action="fileUpload.jsp" method="post" enctype="multipart/form-data">
<table border=1>
	<tr>
		<td colspan=2 align=center><h3>파일 업로드 폼</h3></td>
	<tr>
		<td>올린 사람 : </td><td><input type="text" name="name"></td><br>
	</tr>
	<tr>
		<td>제목 : </td><td><input type="text" name="subject"></td><br>
	</tr>
	<tr>
		<td>파일명1 : </td><td><input type="file" name="fileName1"></td><br>
	</tr>
	<tr>
		<td>파일명2 : </td><td><input type="file" name="fileName2"></td><p/>
	</tr>
	<tr>
		<td colspan=2 align=center><input type="submit" value="전송"></td>
	</tr>
</table>
</form>
</center>
</body>
</html>

- 첨부파일을 서버측으로 전송할 때는 post 방식만 가능하다

- 첨부파일을 서버측으로 전송할 때는 form 태그 안에 반드시 enctype="multipart/form-data" 코드가 있어야한다

- input type=file 로만 해도 버튼이 포함된 이 모양을 만들어준다

+ 첨부파일도 마찬가지로 name 값이 값을 전달하기 위한 변수 역할을 한다

 

- fileUploadForm.jsp 를 실행한 뒤 HTML 시간에 사용했던 사진들을 업로드 하기

 

- 클릭 시 다운로드 된다

 

만약 다시 업로드할때 파일명이 기존 파일명과 중복된다면

- 똑같은 파일명으로 다시 업로드시

- 문제없이 자동으로 잘 처리된다

 

+ 업로드 관련 클래스

- 업로드는 2개의 클래스로 처리된다

- doc 폴더의 index 를 실행하면 실행되는 API 문서에서 클래스 확인 가능

 

- form 태그에서 action 으로 지정된 fileUpload.jsp 파일을 살펴보자

- fileUpload.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="com.oreilly.servlet.MultipartRequest" %>
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %>
<%@ page import="java.util.*" %>
<%

	// 업로드할 디렉토리 위치를 구해옴
	String uploadPath=request.getRealPath("upload");
	System.out.println("path="+uploadPath);	

	int size = 10*1024*1024;	// 첨부파일의 크기(단위:Byte) : 10MB
	String name="";
	String subject="";
	String filename1="";
	String filename2="";
	String origfilename1="";
	String origfilename2="";
	
	try{	// 첨부파일은 MultipartRequest 객체를 생성하면서 업로드 된다.
		MultipartRequest multi=new MultipartRequest(request,
							uploadPath,			// 업로드할 디렉토리 위치
							size, 				// 첨부파일의 크기 : 10MB
							"utf-8",			// 인코딩 타입 설정
				new DefaultFileRenamePolicy());	// 중복 문제 해결

		name=multi.getParameter("name");
		subject=multi.getParameter("subject");
		
		Enumeration files=multi.getFileNames();
		
//	    String file1 = "fileName1";
		String file1 =(String)files.nextElement();
		filename1=multi.getFilesystemName(file1);
		origfilename1= multi.getOriginalFileName(file1);

// 	    String file2 = "fileName2";		
		String file2 =(String)files.nextElement();
		filename2=multi.getFilesystemName(file2);
		origfilename2=multi.getOriginalFileName(file2);
		
	}catch(Exception e){
		e.printStackTrace();
	}
%>
<html>
<body>
<form name="filecheck" action="fileCheck.jsp" method="post">
	<input type="hidden" name="name" value="<%=name%>">
	<input type="hidden" name="subject" value="<%=subject%>">
	<input type="hidden" name="filename1" value="<%=filename1%>">
	<input type="hidden" name="filename2" value="<%=filename2%>">
	<input type="hidden" name="origfilename1" value="<%=origfilename1%>">
	<input type="hidden" name="origfilename2" value="<%=origfilename2%>">
</form>
<a href="#" onclick="javascript:filecheck.submit()">업로드 확인 및 다운로드 페이지 이동</a>
</body>
</html>

- 다른 값과 다르게 첨부파일은 useBean, setProperty, getProperty 로 저장 및 가져오기 불가능함

- 그래서 cos 라이브러리에서 지원되는 2개의 클래스를 사용해서 첨부파일을 처리한다

 

업로드할 디렉토리 위치 구하기 (fileUpload.jsp 부분)

- 파일 업로드 후 upload 폴더로 가보면 파일이 저장되어 있지 않음

- C:\Users\admin\eclipse-workspace\jspproject\WebContent 로 가보면 아무 파일도 없다

	// 업로드할 디렉토리 위치를 구해옴
	String uploadPath=request.getRealPath("upload");
	System.out.println("path="+uploadPath);

- "upload" 라는 폴더를 실제 저장되는 경로로 지정하고 있다

- request.getRealPath() 메소드로 실제 서버에 저장되는 경로값을 구해온다

 

- 실제로 첨부파일이 서버에서 저장되는 위치는 따로 있고, 콘솔창을 보면 확인 가능

C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\jspproject\upload

- 이 위치가 실제 저장 위치이다

- 탐색기에 복붙시

- 같은 파일명인 파일이 들어왔을때는 번호를 뒤에 붙여서 중복문제를 자동으로 해결해 줌

 

cos 라이브러리 업로드 관련 클래스

- 2개의 클래스를 사용해서 업로드

- com.oreilly.servlet.MultipartRequest 클래스 : 실제 업로드를 수행하는 메소드

- com.oreilly.servlet.multipart.DefaultFileRenamePolicy 클래스 : 중복 문제를 해결하는 메소드

 

- 이후 각 클래스 API 문서 볼 것

 

업로드 하기 (fileUpload.jsp 부분)

	// 첨부파일은 MultipartRequest 객체를 생성하면서 업로드 된다.
		MultipartRequest multi=new MultipartRequest(request,
							uploadPath,	// 업로드할 디렉토리 위치
							size, 		// 첨부파일의 크기 : 10MB
							"utf-8",	// 인코딩 타입 설정
				new DefaultFileRenamePolicy());		// 중복 문제 해결

- cos 라이브러리는 따로 업로드 시켜주는 코드가 없고, MultipartRequest 객체를 생성하면서 자동으로 업로드된다

+ 다른 라이브러리는 따로 업로드 시켜주는 코드가 있다

- 매개변수가 5개인 생성자로 MultipartRequest  객체 생성하면서 첨부파일 업로드가 수행됨

- 아래를 보면서 각 매개변수가 무엇을 의미하는지 보자

 

MultipartRequest  생성자 매개변수 의미

- MultipartRequest 클래스 API 문서

MultipartRequest
(javax.servlet.http.HttpServletRequest request,
java.lang.String saveDirectory,
int maxPostSize,
java.lang.String encoding, 
FileRenamePolicy policy)

- 첫번째 매개변수 : request 객체가 되어야함

- 두번째 매개변수 : 업로드할 디렉토리 위치

- 세번째 매개변수 : 최대크기, 바이트 단위, 현재는 10메가까지 설정되어있다

- 네번째 매개변수 : cos 라이브러리로 전달되는 한글값들은 setCharaterEncoding() 메소드를 사용할 필요 없이 여기서 설정 가능

- 다섯번째 매개변수 : DefaultFileRenamePolicy 객체를 생성함으로서 중복 문제를 해결해준다

+ MultipartRequest 객체 생성시 인코딩 시켰으므로 name값과 subject.값이 넘어오더라도 한글값이 깨지지 않는다

 

폼에서 넘어온 첨부파일 외의 다른 값들은 어떻게 받을까? (fileUpload.jsp 부분)

- 폼에서 넘어온 올린 사람, 제목을 받는 방법

 

- request.getParameter() 처럼 request 객체로 값을 받으려고 시도하면 값이 안받아진다

//	String na = request.getParameter("name");
//	out.println("name : " + na);	// name : null (값 전달이 안됨)

 

- 해결 방법 : request 객체가 아닌 생성했던 MultipartRequest 객체인 multi.getParameter() 메소드로 값을 받을 수 있다

		name=multi.getParameter("name");
		subject=multi.getParameter("subject");

 

파일명 구하기 (fileUpload.jsp 부분)

- 사용자가 폼에서 업로드한 첨부파일의 파일명을 구해야한다

- 파일의 name 값을 알지 못하는 경우와 아는 경우로 나뉜다

1. 파일의 name 값을 알지 못하는 경우

- 파일의 name 값인 "fileName1", "fileName2" 를 알지 못하는 경우

- 열거형 Enumeration 에 multi.getFileNames() 로 구해온 이름들을 저장하고 하나씩 가져오기

- multi.getFileNames() 메소드 : input type=file 로 된 양식의 name 값들을 가져옴

		// 열거형 : fileName1, fileName2
		Enumeration files=multi.getFileNames();
		
//	    String file1 = "fileName1";
		String file1 =(String)files.nextElement();
		// 실제 서버에 저장된 파일명 : Koala1.jpg
		filename1=multi.getFilesystemName(file1);
		// 클라이언트가 업로드한 파일명 : Koala.jpg
		origfilename1= multi.getOriginalFileName(file1);

// 	    String file2 = "fileName2";		
		String file2 =(String)files.nextElement();
		filename2=multi.getFilesystemName(file2);
		origfilename2=multi.getOriginalFileName(file2);

- 실제 서버에 저장된 파일명과 클라이언트가 업로드한 파일명이 다를 수 있다

- getFilesystemName() : 실제 서버에 저장된 파일명 구하기 ex) Koala1.jpg

- getOriginalFileName() : 사용자가 실제 선택한 파일명, 클라이언트가 업로드한 파일명 구하기 ex) Koala.jpg

- 중복문제가 없을땐 두가지가 같은 이름, 중복문제가 생겼을때 번호를 붙여 서버에 저장하면 두 이름이 달라짐

 

2. 파일의 name 값을 아는 경우

- 열거형을 사용할 필요 없이 바로 multi.getFilesystemName() 과 getOriginalFileName() 으로 파일명 구하기

 

- MultipartRequest 클래스 지원 메소드

- 지원되는 메소드가 MultipartRequest 안에 있으면 그걸 사용

- 지원되는 메소드가 없으면 request 객체 사용하면 됨 

 

- 이후 폼을 통해서 값을 전달함

 

값을 fileCheck.jsp 파일로 전달 (fileUpload.jsp 부분)

<body>
<form name="filecheck" action="fileCheck.jsp" method="post">
	<input type="hidden" name="name" value="<%=name%>">
	<input type="hidden" name="subject" value="<%=subject%>">
	<input type="hidden" name="filename1" value="<%=filename1%>">
	<input type="hidden" name="filename2" value="<%=filename2%>">
	<input type="hidden" name="origfilename1" value="<%=origfilename1%>">
	<input type="hidden" name="origfilename2" value="<%=origfilename2%>">
</form>
<a href="#" onclick="javascript:filecheck.submit()">업로드 확인 및 다운로드 페이지 이동</a>
</body>

- hidden 을 사용해서 각 파일의 파일명 2개씩과 name, subject 등 6개의 값을 fileCheck.jsp 로 전송

- 전송버튼이 아니라 링크지만 onClick 에 submit() 메소드를 사용해서 전송가능

 

- 이제 fileCheck.jsp 파일을 살펴보자

- fileCheck.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
	request.setCharacterEncoding("utf-8");
	String name=request.getParameter("name");
	String subject=request.getParameter("subject");
	String filename1=request.getParameter("filename1");
	String filename2=request.getParameter("filename2");
	//아래 두줄 추가
	String origfilename1=request.getParameter("origfilename1");
	String origfilename2=request.getParameter("origfilename2");
	
	
%>
<html>
<head>
<title>파일 업로드 확인 및 다운로드</title>
</head>
<body>
올린 사람 : <%=name %><br>
제목 : <%=subject %><br>
<!-- 파일 명 링크 거는 부분 수정함. -->
파일명1 : <a href="file_down.jsp?file_name=<%=filename1 %>"><%=origfilename1 %></a><br>
파일명2 : <a href="file_down.jsp?file_name=<%=filename2 %>"><%=origfilename2 %></a><p>
</body>
</html>

- hidden 으로 넘어온 값들을 모두 변수에 저장함

+ MultipartRequest 객체 생성시 인코딩 시켰으므로 name값과 subject.값이 계속 넘어가더라도 한글값이 깨지지 않는다

- 파일명을 클릭시 링크를 걸어서 클릭시 file_down.jsp 로 이동해서 파일을 다운하도록 한다

- 클릭시 get 방식으로 file_name 에 실제 서버에 저장된 이름인 filename1, filename2 를 각각 file_down.jsp로 전달함

 

 

- file_down.jsp 파일을 살펴보자

- file_down.jsp

<%@ page contentType="text/html;charset=utf-8" %>
<%@ page import="java.io.File"%>
<%@ page import="java.io.*"%>

<%
   String fileName = request.getParameter("file_name");
   System.out.println("fileName="+fileName);

   String savePath = "upload";
   ServletContext context = getServletContext();
   String sDownloadPath = context.getRealPath(savePath);
   String sFilePath = sDownloadPath + "\\" + fileName;
   System.out.println("sFilePath="+sFilePath);
   
   // jsp에서 OutputStream 사용시 IllegalStateException 해결법 : 2줄 추가
   out.clear();
   out = pageContext.pushBody();
   
   try{
   		byte b[] = new byte[4096];
   		File oFile = new File(sFilePath);

   		FileInputStream in = new FileInputStream(sFilePath);

   		String sMimeType = getServletContext().getMimeType(sFilePath);
   		System.out.println("sMimeType>>>"+sMimeType );

   		// 다운로드 파일의 파일형식(마임타입) 설정
   		// octet-stream은 8비트로 된 일련의 데이터를 뜻합니다. 지정되지 않은 파일 형식을 의미합니다. 
   		if(sMimeType == null) sMimeType = "application/octet-stream";

   		response.setContentType(sMimeType);

   		// 한글 파일명 처리 : 한글 파일명이 깨지는 것을 방지해 준다.
   		String sEncoding = new String(fileName.getBytes("utf-8"),"8859_1");

   		// 다운로드 파일 헤드 지정
   		response.setHeader("Content-Disposition", "attachment; filename= " +  sEncoding);
   
   		// 출력 스트림 생성 : 위의 15, 16라인과 충돌발생함.
   		ServletOutputStream out2 = response.getOutputStream();
   		int numRead;

   		// 바이트 배열b의 0번 부터 numRead번 까지 브라우저로 출력
   		while((numRead = in.read(b, 0, b.length)) != -1) {
    		out2.write(b, 0, numRead);
   		}
   		out2.flush(); 
   		out2.close();
   		in.close();
   		
   }catch(Exception e){
	   e.printStackTrace();
   }

%>

- request.getParameter() 를 통해 get 방식으로 넘어온 file_name 값을 받아서 fileName 변수에 저장함 (실제 저장 이름)

 

절대경로 구하기 (file_down.jsp 부분)

- 다운로드 받을때도 upload 디렉토리까지의 절대경로를 구해와야한다

   String savePath = "upload";
   ServletContext context = getServletContext();
   String sDownloadPath = context.getRealPath(savePath);
   String sFilePath = sDownloadPath + "\\" + fileName;
   System.out.println("sFilePath="+sFilePath);

- getServletContext() 로 context 를 구해오고, 이 context 객체는 request 와 같은 역할을 함

- context.getRealPath() 메소드로 upload 폴더의 진짜 경로를 구해와서 sDownloadPath 에 저장

- sDownloadPath 에 저장된 경로값은 upload 디렉토리까지의 절대경로가 출력됨

sFilePath=C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\jspproject\upload\Penguins3.jpg

 

마임 타입 설정 (file_down.jsp 부분)

   		String sMimeType = getServletContext().getMimeType(sFilePath);
   		System.out.println("sMimeType>>>"+sMimeType );

   		// 다운로드 파일의 파일형식(마임타입) 설정
   		// octet-stream은 8비트로 된 일련의 데이터를 뜻합니다. 지정되지 않은 파일 형식을 의미합니다. 
   		if(sMimeType == null) sMimeType = "application/octet-stream";

   		response.setContentType(sMimeType);

- 다운로드 파일의 파일형식(마임타입) 설정하는 코드

- 콘솔창 출력

 

한글 파일명 처리 (file_down.jsp 부분)

   		// 한글 파일명 처리 : 한글 파일명이 깨지는 것을 방지해 준다.
   		String sEncoding = new String(fileName.getBytes("utf-8"),"8859_1");

- 한글 파일명이 깨지는 것을 방지해주는 코드

 

다운로드 파일 헤드 지정 (file_down.jsp 부분)

   		// 다운로드 파일 헤드 지정
   		response.setHeader("Content-Disposition", "attachment; filename= " +  sEncoding);

- 다운로드 받은 파일의 헤더에 관한 설정

- 인코딩 값등 설정

 

첨부파일을 하나씩 읽어오기 / 다운로드 (file_down.jsp 부분)

   		// 출력 스트림 생성 : 위의 15, 16라인과 충돌발생함.
   		ServletOutputStream out2 = response.getOutputStream();
   		int numRead;

   		// 바이트 배열b의 0번 부터 numRead번 까지 브라우저로 출력
   		while((numRead = in.read(b, 0, b.length)) != -1) {
    		out2.write(b, 0, numRead);
   		}
   		out2.flush(); 
   		out2.close();
   		in.close();

- 첨부파일을 하나씩 읽어오는 작업을 한다

- 읽어올때는 read() 메소드 사용

- 출력스트림을 통해서 write() 함수를 사용시 다운로드가 수행

- 쭉 읽어와서 출력을 시키면 그게 다운로드이다

 


Model 1 DBCP 방식으로 자료실 게시판 만들기

 

자료실 게시판

- 일반게시판에서 첨부파일을 업로드 / 다운로드 기능을 추가한 게시판

 

자료실 게시판 : 주요 기능 소개

1. Connection Pool

2. 액션태그

- <jsp:useBean....>, <jsp:setProperty....>

+ 첨부파일이 있는 경우엔 useBean, setProperty 사용 불가, 없는 경우엔 사용 가능

3. DTO, DAO 클래스

4. 페이징 처리 ( inline View )

5. 첨부파일 업로드 – cos.jar 라이브러리 이용

 

첨부파일 관련 컬럼

1. upload 컬럼

- 테이블에 첨부파일명을 저장해야한다

- 컬럼 upload 을 추가하여, 그 컬럼에 첨부파일명을 저장한다 

- 나머지 컬럼은 일반 게시판과 같다

 

자료실 게시판 : 프로젝트 생성

- 이렇게 구조를 만들기 위해

- upload 라는 프로젝트부터 생성

- 이후 update 프로젝트의 WebContent 폴더 하위에 초기에 보여줄 파일인 index.jsp 를 생성

 

자료실 게시판 : 기초 파일 가져오기

- 클라우드에서 작성 양식, 환경설정 코드가 있는 upload 폴더를 이클립스 WebContent 폴더로 가져오기

 

자료실 게시판 : 첨부파일을 저장할 폴더 생성

- WebContent 하위에 업로드된 파일을 저장할 폴더인 upload 를 생성

- 실제 저장되는 폴더는 아니지만 여기에 upload 폴더를 만들어야 실제 서버에 저장되는 위치에도 upload 폴더가 생성된다

 

자료실 게시판 : 몇가지 환경 구축

1) reboard 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

- 커넥션 풀의 환경설정 파일이고, 기존에 작성했던 내용

2) 오라클용 JDBC Driver 인 ojdbc.jar 를 WEB-INF/lib 폴더 안에 가져오기, 다른 프로젝트에서 가져오면 된다

3) cos 라이브러리를 사용하기 위해 cos.jar 파일을 WEB-INF/lib 폴더 안으로 가져오기

 

자료실 게시판 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

- context.xml

<Context> 
  <Resource name="jdbc/orcl" 
   auth="Container"
   type="javax.sql.DataSource" 
   username="totoro" 
   password="totoro123"
   driverClassName="oracle.jdbc.driver.OracleDriver"
   factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"
   url="jdbc:oracle:thin:@localhost:1521:xe"
   maxActive="500"  
   maxIdle="100"/>  
</Context>

<!--  p423 참조
 maxActive="500"  컨넥션풀이 관리하는 컨넥션의 최대 갯수
		          (기본값 : 8)
 maxIdle="100" 	  컨넥션풀이 관리하는 최대 유휴 갯수(기본값 : 8)          
-->

 

- dbcpAPITest.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*" %>
<%@ page import="javax.naming.*" %>
<%
 	Connection conn = null; 
	
	try {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();
  		
  		out.println("<h3>연결되었습니다.</h3>");
	}catch(Exception e){
		out.println("<h3>연결에 실패하였습니다.</h3>");
		e.printStackTrace();
 	}
%>

- 커넥션 풀에서 커넥션 가져오기 테스트 성공

+ 추후 DAO 클래스 만들때 커넥션풀에서 커넥션 구하기 3줄 코드를 DAO 클래스에 넣을 것

  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();

 

자료실 게시판 : 테이블 및 시퀀스 생성

- 이전에 회원관리 프로그램에서 사용했던 오라클계정인 totoro 계정에 upload 테이블 생성

- 아래 양식으로 테이블을 생성한다

create table upload( 
	num number primary key,
	writer varchar2(20) not null,
	email varchar2(30),
	subject varchar2(50) not null,
	passwd varchar2(20) not null,
	reg_date timestamp not null,
	readcount number default 0,
	content varchar2(2000) not null,
	ip varchar2(20) not null,
	upload varchar2(30) ); -- 첨부파일명

create sequence upload_seq
	start with 1
	increment by 1
	nocache;

 

- WebContent 하위에 sql 폴더를 만들고 안에 upload.sql 파일 생성

- 커넥션 프로파일을 설정 : Oracle_11, New Oracle(totoro), xe 로 해준다

- 테이블과 시퀀스를 생성한다

- upload 컬럼에는 첨부파일 명이 들어간다.

- upload 컬럼을 제외한 나머지 컬럼은 일반 게시판과 동일하다

- 시퀀스는 num 이라는 기본키 컬럼에 들어갈 시퀀스이다

- 테이블과 시퀀스 생성 확인

 

- 생성확인 완료

 

자료실 게시판 : DAO 와 DTO 클래스 만들기

+ 확장자가 .java 인 파일은 src 폴더 안에 있어야 정상동작한다

+ 이후 Model 2 에선 기능에 따라 세분화하므로 DAO, DTO 가 다른 패키지에 들어감

- 지금은 같은 패키지인 upload 패키지 안에 둘 다 넣자

 

자료실 게시판 : DTO 클래스 작성

- 이걸 가져와서 DTO 클래스에 복붙

- varchar2 는 String 으로, number 는 int 로, timestamp 는 Timestamp로 바꿔서 자바 변수(프로퍼티) 만들기

+ 프로퍼티의 접근제어자는 private

+ java.sql 의 Timestamp import

- getter / setter 메소드 추가

 

- DTO 클래스 BoardDataBean.java 완성 코드

// DTO (Data Transfer Object)

package upload;

import java.sql.Timestamp;

public class BoardDataBean {
	private int num;
	private String writer;
	private String email;
	private String subject;
	private String passwd;
	private Timestamp reg_date;
	private int readcount;
	private String content;
	private String ip;
	private String upload;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public Timestamp getReg_date() {
		return reg_date;
	}
	public void setReg_date(Timestamp reg_date) {
		this.reg_date = reg_date;
	}
	public int getReadcount() {
		return readcount;
	}
	public void setReadcount(int readcount) {
		this.readcount = readcount;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	public String getUpload() {
		return upload;
	}
	public void setUpload(String upload) {
		this.upload = upload;
	}
}

 

자료실 게시판 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2. 정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 자료실 게시판 CRUD 관련 메소드

 

1. 싱글톤

- 외부 접근 불가능하게 private, 공유하기 위해 static 해서 자기자신의 클래스로 객체 생성을 한번만 하기

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드 getConnection()

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- DBCP 방식으로 DB 와 연결하므로 이 DB 연결시 커넥션을 구해오는 이 메소드를 사용해야한다

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 dbcpAPITest.jsp 3줄을 복사해서 넣는다

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

+ Context 와 DataSource 둘 다 Interface 이다

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

+ 리턴자료형을 private 으로 해서 DAO 클래스 내에서만 호출가능하도록 함

 

- 현재까지 DAO 클래스 BoardDBBean.java 코드

// DAO (Data Access Object)

package upload;

import java.sql.Connection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class BoardDBBean {
	
	// 싱글톤 : 객체 생성을 1번만 수행하는 것.
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean getInstance() {
		return instance;
	}
	
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	private Connection getConnection() throws Exception{
		
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 글 작성
}

 

4. 그 이후 자료실 게시판 CRUD 관련 메소드

- 가장 먼저 글 작성 메소드를 만들어야한다, 그 후 다양한 메소드들을 DAO에 작성

 

자료실 게시판 : 파일별 기능 및 구조 설명

- writeForm.jsp 에 첨부파일을 선택하는 양식이 포함되어있다

+ Recent posts