자바(JAVA)/JSP 웹 프로그래밍 공부 (성낙현의 JSP 자바 웹 프로그래밍 참고)

JAVA/JSP 43. 모델2 방식(MVC 패턴)의 자료실형 게시판 만들기 - 파일 다운로드, 삭제하기, 수정하기

개발학생 2025. 5. 9. 18:57
반응형

*JAVA/JSP 35번 글의 시점부터는 노트북이 고장나는 바람에.. 다른 분의 글과 이전에 올렸던 글들을 참고하여, 다른 데스크탑의 윈도우 환경에 환경설정을 다시 진행한 후 작성하였습니다. 환경설정 관련 글들은 아래를 참고해 주세요.

  • JDK11 설치 및 환경 변수(JAVA_HOME) 설정 - CLASSPATH는 추가하지 않음
 

자바(JAVA) 11 설치 및 환경설정 | 자바 11 환경 변수

자바로 코딩 공부를 하려면 개발 할 수 있는 환경을 만들어 주어야 한다. 그 순서를 먼저 작성하자면JAVA SE JDK 설치 -> JAVA 환경 변수 설정 -> IDE 설치 이렇게 볼 수 있겠다. 이 포스팅은 자바로 개

velog.io

  • 톰캣 설치, 이클립스 설치 - 이 글을 쓰는 시점에서는 Tomcat 8버전이 없어, Tomcat 9버전 설치
 

JSP) JDK, 이클립스(eclipse), 톰캣 설치 및 환경 설정

1. JDK 설치 → Path 설정 2.eclipse JAVA EE버전 다운로드 3.웹컨테이너 톰캣 설치 →톰캣 환경설정 1. JDK 설치 - JAVA) 1. JDK 참고하여 설치하고 설정한다. -https://storyblogger.tistory.com/15?category=984696 2. 구글에

storyblogger.tistory.com

  • 이클립스 JDK 버전 설정 - JDK11로 설정
 

[이클립스] JDK 버전 바꾸는 법

스프링을 사용하는 에러가 난다? 그러면 JRE 버전을 확인해보자! 프로젝트의 JRE System Library의 오른쪽을 살펴보면 [JavaSE-17]이라고 되어있다. 스프링이나 하이버네이트를 사용할 때, 자바 8이나 자

myvelop.tistory.com

  • JDK 설치 및 환경 변수(JAVA_HOME) 설정, 톰캣 설치, 이클립스 설치 및 JDK 버전 설정 이후~ ⇒ 톰캣 버전과 jsp 파일 경로는 글 내용과 다르게 아래 이미지와 같음

 

JAVA/JSP 1. 개발 환경 구축 - 이클립스 기본 설정

*JDK 설치 및 환경 변수(JAVA_HOME) 설정, 톰캣 설치, 이클립스 설치를 완료한 상태OpenJDK 11: 자바 프로그램을 컴파일하고 실행해주는 기본 도구*이클립스 JDK-17이 설치되어있어서, https://coding-house.tisto

keep-programming-study.tistory.com

 

 

JAVA/JSP 1. 개발 환경 구축 - JSP 예제 테스트

1. 프로젝트 생성1) [File] → [New] → [Dynamic Web Project] 선택 2) 프로젝트 설정 화면 → Project name: HelloJSP, Target runtime: Apache Tomcat v9.0, Dynamic web module version: 4.0으로 설정 후 Next 클릭 3) 자바 소스 파일

keep-programming-study.tistory.com

 

 

JAVA/JSP 1. 개발 환경 구축 - 마지막 추가 설정

1. 외부 웹 브라우저로 실행하기-크롬이클립스는 웹 애플리케이션 실행 시, 자체 브라우저를 통해 실행 결과 출력웹 애플리케이션 배포 시 사용자는 크롬/파이어폭스 같은 전용 웹 브라우저로

keep-programming-study.tistory.com

  • 오라클(Oracle Database 11gR2 Express Edition) 설치 및 설정 
 

JAVA/JSP 14. 데이터베이스 - 특징, 오라클 설치(Oracle Database 11gR2 Express Edition), 사용자 계정 생성 및

1. 데이터베이스의 특징우리가 매일 PC나 스마트폰을 통해 접하는 거의 모든 웹 애플리케이션에서 사용함매일 업데이트되는 뉴스나 날씨 등의 정보는 데이터베이스가 없다면 클라이언트에 전달

keep-programming-study.tistory.com

 

 

 

JAVA/JSP 15. 데이터베이스 - 테이블 및 시퀀스 생성, JDBC 설정 및 데이터베이스 연결

*회원제 게시판 만들기-회원 인증 필요4. 테이블 및 시퀀스 생성1) 테이블 생성(1) member 테이블 - 아이디, 패스워드, 이름, 가입 날짜테이블 정의컬럼명데이터 타입 null 허용 키 기본값 설명idvarchar2

keep-programming-study.tistory.com

 

JAVA/JSP 16. 데이터베이스 - 커넥션 풀로 성능 개선, 간단한 쿼리 작성 및 실행

6. 커넥션 풀로 성능 개선웹은 클라이언트의 요청에 서버가 응답하는 구조→ Connection 객체 생성 때마다 네트워크 통신이 이뤄지며, 시간이 걸리는 작업들이 수반됨 == 시스템 성능에 큰 영향을

keep-programming-study.tistory.com

  • 시퀀스 설정
 

JAVA/JSP 15. 데이터베이스 - 테이블 및 시퀀스 생성, JDBC 설정 및 데이터베이스 연결

*회원제 게시판 만들기-회원 인증 필요4. 테이블 및 시퀀스 생성1) 테이블 생성(1) member 테이블 - 아이디, 패스워드, 이름, 가입 날짜테이블 정의컬럼명데이터 타입 null 허용 키 기본값 설명idvarchar2

keep-programming-study.tistory.com


1. 파일 다운로드

  • 목록 보기나 상세 보기 화면에서 첨부 파일의 [다운로드] 링크 클릭 시 실행

1) 모델 작성 - DAO에 메서드 추가

// Java Resources/src/mvcboard/MVCBoardDAO.java

package mvcboard;

... 임포트 생략 ...

public class MVCBoardDAO extends DBConnPool {  // 커넥션 풀 상속
  ... 생략 ...
  // 일련번호를 인수로 받아 다운로드 횟수 1 증가
  public void downCountPlus(String idx) {
    String query = "UPDATE mvcboard SET "
               + "downcount = downcount+1"
               + "where idx=?";
    
    try {
      psmt = con.prepareStatement(query);
      psmt.setString(1, idx);
      psmt.executeUpdate();
    } catch (Exception e) {}
  }  
}

2) FileUtil.java 메서드 추가 

// Java Resources/src/file/FileUtil.java

package file;

... 임포트문 생략 ...

public class FileUtil {
  ... 생략 ...
  // 명시한 파일을 찾아 다운로드
  public static void download(HttpServletRequest req, HttpServletResponse resp, 
   String directory, String sfileName, String ofileName) {
    String sDirectory = req.getServletContext().getRealPath(directory);
    try {
      // 파일을 찾아 입력 스트림 생성
      File file = new File(sDirectory, sfileName);
      InputStream iStream = new FileInputStream(file);

      // 한글 파일명 깨짐 방지
      String client = req.getHeader("User-Agent");
      if (client.indexOf("WOW64") == -1) {
        ofileName = new String(ofileName.getBytes("UTF-8"), "ISO-8859-1");
      } else {
        ofileName = new String(ofileName.getBytes("KSC5601"), "ISO-8859-1");
      }

      // 파일 다운로드용 응답 헤더 설정
      resp.reset();
      resp.setContentType("application/octet-stream");
      resp.setHeader("Content-Disposition",
                       "attachment; filename=\"" + ofileName + "\"");
      resp.setHeader("Content-Length", "" + file.length() );

      // response 내장 객체로부터 새로운 출력 스트림 생성
      OutputStream oStream = resp.getOutputStream();

      // 출력 스트림에 파일 내용 출력
      byte b[] = new byte[(int)file.length()];
      int readBuffer = 0;
      while ( (readBuffer = iStream.read(b)) > 0 ) {
        oStream.write(b, 0, readBuffer);
      }

      // 입/출력 스트림 닫음
      iStream.close();
      oStream.close();
    } catch (FileNotFoundException e) {
      System.out.println("파일을 찾을 수 없습니다.");
      e.printStackTrace();
    } catch (Exception e) {
      System.out.println("예외가 발생하였습니다.");
      e.printStackTrace();
    }
  }
}

3) 컨트롤러(파일 다운로드 서블릿) 작성

// Java Resources/src/mvcboard/DownloadController.java
package mvcboard;

.. 임포트문 생략 ...
import file.FileUtil;

@WebServlet("/board/download.board")
public class DownloadController extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    // 매개변수 받기
    String ofile = req.getParameter("ofile");  // 원본 파일명
    String sfile = req.getParameter("sfile");  // 저장된 파일명
    String idx = req.getParameter("idx");      // 게시물 일련번호

    // 파일 다운로드
    FileUtil.download(req, resp, "/Uploads", sfile, ofile);

    // 해당 게시물의 다운로드 수 1 증가
    MVCBoardDAO dao = new MVCBoardDAO();
    dao.downCountPlus(idx);
    dao.close();
  }
}

4) Default.jsp 실행 후 동작 확인

(1) 오류 1: 게시물 조회수 업데이트 중 예외 발생
-> 어제 몰랐던 오류를 발견했다. DAO 메서드의 SQL 쿼리문에서,  SET 키워드 다음에 공백이 없어 문제가 생겼다.  

java.sql.SQLSyntaxErrorException: ORA-00971: missing SET keyword

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:440)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:837)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:445)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:191)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:523)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1010)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1315)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3576)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3620)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1491)
at org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:123)
at org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:123)
at mvcboard.MVCBoardDAO.updateVisitCount(MVCBoardDAO.java:157)
at mvcboard.ViewController.service(ViewController.java:21)
...

  • 공백을 추가한 후 조회수가 증가하는 모습. 

(2) 파일이 다운로드되고 다운로드 횟수가 증가한 모습(다운로드를 두 번 클릭하여 2로 증가한 것) 

2. 삭제하기 

1) 요청명/서블릿 매핑 컨트롤러(서블릿) 작성

  • mode 매개변수 값을 request 영역에 저장한 다음, Pass.jsp로 포워드 
// Java Resources/src/mvcboard/PassController.java
package mvcboard;

.. 임포트문 생략 ...
@WebServlet("/board/pass.board")
public class PassController extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    req.setAttribute("mode", req.getParameter("mode"));
    req.getRequestDispatcher("/board/Pass.jsp").forward(req, resp);
  }
}

2) 뷰(JSP 파일) 작성

<!-- src/main/webapp/board/Pass.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 첨부형 게시판</title>
<script type="text/javascript">
  function validateForm(form) {
    if (form.pass.value == "") {
      alert("비밀번호를 입력하세요.");
      form.pass.focus();
      return false;
    }
  }
</script>
</head>
<body>    
<h2>파일 첨부형 게시판 - 비밀번호 검증(Pass)</h2>
<form name="writeFrm" method="post" action="../board/pass.board" onsubmit="return validateForm(this);">
<input type="hidden" name="idx" value="${ param.idx }" />
<input type="hidden" name="mode" value="${ param.mode }" />
<table border="1" width="90%">
  <tr>
    <td>비밀번호</td>
    <td>
      <input type="password" name="pass" style="width:100px;" />
    </td>
  </tr>
  <tr>
    <td colspan="2" align="center">
      <button type="submit">검증하기</button>
      <button type="reset">초기화</button>
      <button type="button" onclick="location.href='../board/list.board';">
        목록 바로가기
      </button>
    </td>
  </tr>
</table>    
</form>
</body>
</html>

3) 모델 작성(DAO 클래스에 메서드 추가)

// Java Resources/src/mvcboard/MVCBoardDAO.java

package mvcboard;

... 임포트 생략 ...

public class MVCBoardDAO extends DBConnPool {  // 커넥션 풀 상속
  ... 생략 ...
    // 입력한 비밀번호가, 특정 일련번호 게시물의 비밀번호와 일치하는지 확인
  public boolean confirmPassword(String pass, String idx) {
    boolean isCorr = true;
    
    try {
      String query = "SELECT COUNT(*) FROM mvcboard WHERE pass=? AND idx=?";
      psmt = con.prepareStatement(query);
      psmt.setString(1, pass);
      psmt.setString(2, idx);
      rs = psmt.executeQuery();
      rs.next();
      
      if (rs.getInt(1) == 0) {
        isCorr = false;
      }
    } catch (Exception e) {
      isCorr = false;
      e.printStackTrace();
    }
    return isCorr;
  }

  // 지정한 일련번호의 게시물 삭제
  public int deletePost(String idx) {
    int result = 0;
    
    try {
      String query = "DELETE FROM mvcboard WHERE idx=?";
      psmt = con.prepareStatement(query);
      psmt.setString(1, idx);
      result = psmt.executeUpdate();
    } catch (Exception e) {
      System.out.println("게시물 삭제 중 예외 발생");
      e.printStackTrace();
    }
    return result;
  }
}

4) FileUtil.java 메서드 추가

// Java Resources/src/file/FileUtil.java

package file;

... 임포트문 생략 ...

public class FileUtil {
  ... 생략 ...
  // 지정한 위치의 파일 삭제
  public static void deleteFile(HttpServletRequest req,
   String directory, String filename) {
    String sDirectory = req.getServletContext().getRealPath(directory);
    File file = new File(sDirectory + File.separator + filename);
    if (file.exists()) file.delete();
  }
}

5) 컨트롤러(서블릿) 추가 작성

// Java Resources/src/mvcboard/PassController.java
package mvcboard;

.. 임포트문 생략 ...

@WebServlet("/board/pass.board")
public class PassController extends HttpServlet {
  ... 생략 ...
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    // 매개변수 저장
    String idx = req.getParameter("idx");
    String mode = req.getParameter("mode");
    String pass = req.getParameter("pass");

    // 비밀번호 확인
    MVCBoardDAO dao = new MVCBoardDAO();
    boolean confirmed = dao.confirmPassword(pass, idx);
    dao.close();

    if (confirmed) {  // 비밀번호 일치
      if (mode.equals("edit")) {  // 수정 모드
        HttpSession session = req.getSession();
        session.setAttribute("pass", pass);
        resp.sendRedirect("../board/edit.board?idx=" + idx);
      } else if (mode.equals("delete")) {  // 삭제 모드
        dao = new MVCBoardDAO();
        MVCBoardDTO dto = dao.selectView(idx);
        int result = dao.deletePost(idx);  // 게시물 삭제
        dao.close();

        if (result == 1) {  // 게시물 삭제 성공 시 첨부파일도 삭제
          String saveFileName = dto.getSfile();
          FileUtil.deleteFile(req, "/Uploads", saveFileName);
        }
        JSFunction.alertLocation(resp, "삭제되었습니다.", "../mvcboard/list.do");
      }
    } else {  // 비밀번호 불일치
      JSFunction.alertBack(resp, "비밀번호 검증에 실패했습니다.");
    }
  }
}

6) Default.jsp 실행 후 동작 확인

틀린 비밀번호 입력

올바른 비밀번호 입력(2345): 알림창이 안 뜨고 삭제처리가 안되는 오류발생?? 콘솔창에 오류가 뜨지 않음 
-> JSFunction.java의 alertLocation 메서드 예외처리 부분 catch (Exception e) {} 안에 System.out.println("Exception: " + e.getMessage()); 
e.printStackTrace(); 추가해도 오류가 뭔지 뜨지 않음... 

View.jsp의 파라미터에 오타가 있었음... 오타 수정 후 오류 해결!: model -> mode로 수정 

3. 수정하기 

1) 요청명/서블릿 매핑 컨트롤러(서블릿) 작성

// Java Resources/src/mvcboard/EditController.java
package mvcboard;

.. 임포트문 생략 ...
@WebServlet("/board/edit.board")
public class EditController extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    String idx = req.getParameter("idx");
    MVCBoardDAO dao = new MVCBoardDAO();
    MVCBoardDTO dto = dao.selectView(idx);
    req.setAttribute("dto", dto);
    req.getRequestDispatcher("/board/Edit.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    // 파일 업로드 처리
    // 업로드 디렉토리의 물리적 경로 확인
    String saveDirectory = req.getServletContext().getRealPath("/Uploads");

    // 초기화 매개변수로 설정한 첨부 파일 최대 용량 확인
    ServletContext application = this.getServletContext();
    int maxPostSize = Integer.parseInt(application.getInitParameter("maxPostSize"));

    // 파일 업로드
    MultipartRequest mr = FileUtil.uploadFile(req, saveDirectory, maxPostSize);

    // 파일 업로드 실패
    if (mr == null) {
      JSFunction.alertBack(resp, "첨부 파일이 제한 용량을 초과합니다.");
      return;
    }

    // 파일 업로드 외 처리
    // 수정 내용을 매개변수에서 얻어옴
    String idx = mr.getParameter("idx");
    String prevOfile = mr.getParameter("prevOfile");
    String prevSfile = mr.getParameter("prevSfile");

    String name = mr.getParameter("name");
    String title = mr.getParameter("title");
    String content = mr.getParameter("content");
          
    // 비밀번호는 session에서 가져옴
    HttpSession session = req.getSession();
    String pass = (String)session.getAttribute("pass");

    // DTO에 저장
    MVCBoardDTO dto = new MVCBoardDTO();
    dto.setIdx(idx);
    dto.setName(name);
    dto.setTitle(title);
    dto.setContent(content);
    dto.setPass(pass);
          
    // 원본 파일명과 저장된 파일 이름 설정
    String fileName = mr.getFilesystemName("ofile");
    if (fileName != null) {
      // 첨부 파일이 있을 경우 파일명 변경
      // 새로운 파일명 생성
      String now = new SimpleDateFormat("yyyyMMdd_HmsS").format(new Date());
      String ext = fileName.substring(fileName.lastIndexOf("."));
      String newFileName = now + ext;

      // 파일명 변경
      File oldFile = new File(saveDirectory + File.separator + fileName);
      File newFile = new File(saveDirectory + File.separator + newFileName);
      oldFile.renameTo(newFile);

      // DTO에 저장
      dto.setOfile(fileName);  // 원래 파일 이름
      dto.setSfile(newFileName);  // 서버에 저장된 파일 이름

      // 기존 파일 삭제
      FileUtil.deleteFile(req, "/Uploads", prevSfile);
    } else {
      // 첨부 파일이 없으면 기존 이름 유지
      dto.setOfile(prevOfile);
      dto.setSfile(prevSfile);
    }

    // DB에 수정 내용 반영
    MVCBoardDAO dao = new MVCBoardDAO();
    int result = dao.updatePost(dto);
    dao.close();

    // 성공 or 실패?
    if (result == 1) {  // 수정 성공
      session.removeAttribute("pass");
      resp.sendRedirect("../board/view.board?idx=" + idx);
    } else {  // 수정 실패
      JSFunction.alertLocation(resp, "비밀번호 검증을 다시 진행해주세요.",
      "../board/view.board?idx=" + idx);
    }
  }
}

2) 뷰(JSP 파일) 작성

<!-- src/main/webapp/board/Edit.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 첨부형 게시판</title>
</head>
<h2>파일 첨부형 게시판 - 수정하기(Edit)</h2>
<form name="writeFrm" method="post" enctype="multipart/form-data" action="../board/edit.board">
<input type="hidden" name="idx" value="${ dto.idx }"/>
<input type="hidden" name="prevOfile" value="${ dto.ofile }" />
<input type="hidden" name="prevSfile" value="${ dto.sfile }" />
    
<table border="1" width="90%">
  <!-- 필수 입력값인 작성자/제목/내용의 유효성 검사는, 모두 html의 required로 처리 -->
  <tr>
    <td>작성자</td>
    <td>
      <input type="text" name="name" style="width:150px;" value="${ dto.name }" required />
    </td>
  </tr>
  <tr>
    <td>제목</td>
    <td>
      <input type="text" name="title" style="width:90%;" value="${ dto.title }" required/>
    </td>
  </tr>
  <tr>
    <td>내용</td>
    <td>
      <textarea name="content" style="width:90%;height:100px;" required>${ dto.content }</textarea>
    </td>
  </tr>
  <tr>
    <td>첨부 파일</td>
    <td>
      <input type="file" name="ofile" />
    </td>
  </tr>
  <tr>
    <td colspan="2" align="center">
       <button type="submit">작성 완료</button>
       <button type="reset">모두 초기화</button>
       <button type="button" onclick="location.href='../board/list.board';">
         목록 바로가기
       </button>
    </td>
    </tr>
</table>    
</form>
</body>
</html>

3) 모델 작성(DAO에 메서드 추가)

// Java Resources/src/mvcboard/MVCBoardDAO.java

package mvcboard;

... 임포트 생략 ...

public class MVCBoardDAO extends DBConnPool {  // 커넥션 풀 상속
  ... 생략 ...

  // 게시글 데이터를 받아 DB에 저장되어 있던 내용을 수정(파일 업로드 지원).
  public int updatePost(MVCBoardDTO dto) {
    int result = 0;
    
    try {
      // 쿼리문 템플릿 준비
      String query = "UPDATE mvcboard"
                   + " SET title=?, name=?, content=?, ofile=?, sfile=? "
                   + " WHERE idx=? and pass=?";

      // 쿼리문 준비
      psmt = con.prepareStatement(query);
      psmt.setString(1, dto.getTitle());
      psmt.setString(2, dto.getName());
      psmt.setString(3, dto.getContent());
      psmt.setString(4, dto.getOfile());
      psmt.setString(5, dto.getSfile());
      psmt.setString(6, dto.getIdx());
      psmt.setString(7, dto.getPass());

      // 쿼리문 실행
      result = psmt.executeUpdate();
    } catch (Exception e) {
      System.out.println("게시물 수정 중 예외 발생");
      e.printStackTrace();
    }
    return result;
  }
}

4) Default.jsp 실행 후 동작 확인 (비밀번호 1234, 틀린 비밀번호 입력 시 알림은 삭제하기와 같음)

 

반응형