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

JAVA/JSP 42. 모델2 방식(MVC 패턴)의 자료실형 게시판 만들기 - 파일 업로드 포함 글쓰기, 상세 보기

개발학생 2025. 5. 8. 18:36
반응형

*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. 파일 업로드 포함 글쓰기(서블릿 매핑에 web.xml 사용)

1) 요청명/서블릿 매핑(web.xml)

<!-- src/main/webapp/WEB-INF/web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>HelloJSP</display-name>
  ... 생략 ...
  <servlet> <!-- 서블릿 등록 -->
    <servlet-name>MVCBoardWrite</servlet-name>  <!-- 1. 서블릿 매핑을 위한 서블릿명 -->
    <servlet-class>mvcboard.WriteController</servlet-class>  <!-- 2. 요청을 처리할 서블릿을 패키지를 포함하여 명시 -->
  </servlet>
  <servlet-mapping>  <!-- 서블릿과 요청명 매핑 -->
    <servlet-name>MVCBoardWrite</servlet-name>  <!-- 3. 1과 동일한 서블릿명 -->
    <url-pattern>/board/write.board</url-pattern>  <!-- 4. 컨텍스트 루트를 제외한 요청명 작성 -->
  </servlet-mapping>
</web-app>

2) 업로드할 파일의 제한 용량을 컨텐스트 초기화 매개변수로 추가(web.xml)

<!-- src/main/webapp/WEB-INF/web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>HelloJSP</display-name>
  ... 생략 ...
  <!-- 업로드할 파일의 제한 용량: 1024바이트 x 1000 = 1MB -->
  <context-param>
    <param-name>maxPostSize</param-name>
    <param-value>1024000</param-value>
  </context-param>
</web-app>

3) 컨트롤러 작성 1 - 작성 폼으로 진입

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

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class WriteController extends HttpServlet{
  //1. 글쓰기 페이지로 진입
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
    req.getRequestDispatcher("/board/Write.jsp").forward(req, resp);
  }
}

4) 뷰(JSP 파일) 작성

<!-- src/main/webapp/board/Write.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 첨부형 게시판</title>
</head>
<body>
  <h2>파일 첨부형 게시판 - 글쓰기</h2>
  <form name="writeFrm" method="post" enctype="multipart/form-data"
   action="../board/write.board">
    <table border="1" width="90%">
      <tr>
        <td>작성자</td>
        <td>
          <!-- 필수 항목 입력 확인을 required로 진행 -->
          <input type="text" name="name" style="width:150px;" required />
        </td>
      </tr>
      <tr>
        <td>제목</td>
        <td>
          <!-- 필수 항목 입력 확인을 required로 진행 -->
          <input type="text" name="title" style="width:90%;" required />
        </td>
      </tr>
      <tr>
        <td>내용</td>
        <td>
          <!-- 필수 항목 입력 확인을 required로 진행 -->
          <textarea name="content" style="width:90%; height:100px;" required></textarea>
        </td>
      </tr>
      <tr>
        <td>첨부 파일</td>
        <td>
          <input type="file" name="ofile" />
        </td>
      </tr>
      <tr>
        <td>비밀번호</td>
        <td>
          <!-- 필수 항목 입력 확인을 required로 진행 -->
          <input type="password" name="pass" style="width:100px;" required />
        </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>

JSP 파일 실행 결과

5) 모델 작성(DAO에 기능 추가)

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

package mvcboard;

import java.util.List;
import java.util.Map;
import java.util.Vector;

import common.DBConnPool;

public class MVCBoardDAO extends DBConnPool {  // 커넥션 풀 상속
  ... 생략 ...
	// 게시글 데이터를 받아 DB에 추가(파일 업로드 지원)
	// 1. Write.jsp에서 전송한 폼값을 서블릿이 받아 DTO에 저장한 후 DAO로 전달
	public int insertWrite(MVCBoardDTO dto) {
	  int result = 0;
	  
	  try {
	  	// 2. INSERT 쿼리문
	    String query = "INSERT INTO mvcboard ("
	                 + "idx, name, title, content, ofile, sfile,pass) "
	                 + "VALUES ("
	                 + "seq_board_num.NEXTVAL, ?, ?, ?, ?, ?, ?)";
	    
	    // 3. 쿼리문을 인수로 PreparedStatement 객체 생성 후 인파라미터 설정 
	    psmt = con.prepareStatement(query);
	    psmt.setString(1, dto.getName());
	    psmt.setString(2, dto.getTitle());
	    psmt.setString(3, dto.getContent());
	    psmt.setString(4, dto.getOfile());
	    psmt.setString(5, dto.getSfile());
	    psmt.setString(6, dto.getPass());
	    
	    // 4. 쿼리문을 실행하여 테이블에 입력
	    result = psmt.executeUpdate();
	  } catch (Exception e) {
	    System.out.println("게시물 입력 중 예외 발생");
	    e.printStackTrace();
	  }
	  // 5. 입력된 결과를 서블릿으로 반환
	  return result;
	}  
}

6) 컨트롤러 작성 2 - 폼값 처리(doPost)

(1) 파일 업로드용 유틸리티 클래스 작성

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

import javax.servlet.http.HttpServletRequest;

import com.oreilly.servlet.MultipartRequest;

public class FileUtil {
  // 파일 업로드(multipart/form-data 요청) 처리
  public static MultipartRequest uploadFile(HttpServletRequest req,
   String saveDirectory, int maxPostSize) {
    try {
      // 파일 업로드
      return new MultipartRequest(req, saveDirectory, maxPostSize, "UTF-8");
    } catch (Exception e) {
      // 파일 업로드 실패
      e.printStackTrace();
      return null;
    }
  }
}

(2) 컨트롤러에 글쓰기 메서드 추가

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

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

public class WriteController extends HttpServlet{
  ... 생략 ...
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
    // 파일 업로드 처리
    // 1. 업로드 디렉토리의 물리적 경로 확인 
    String saveDirectory = req.getServletContext().getRealPath("/Uploads");
  	
    // 2. web.xml에 초기화 매개변수로 설정한, 첨부 파일 최대 용량 확인
    ServletContext application = getServletContext();
    int maxPostSize = Integer.parseInt(application.getInitParameter("maxPostSize"));
    
    // 3. 파일 업로드
    MultipartRequest mr = FileUtil.uploadFile(req, saveDirectory, maxPostSize);
    if (mr == null) {
      // 4. 파일 업로드 실패
    	JSFunction.alertLocation(resp, "첨부 파일이 제한 용량을 초과합니다.", "../board/write.board");
      
    	return;
    }
    
    // 파일 업로드 외 처리
    // 5. 폼값을 DTO에 저장
    MVCBoardDTO dto = new MVCBoardDTO();
    dto.setName(mr.getParameter("name"));
    dto.setTitle(mr.getParameter("title"));
    dto.setContent(mr.getParameter("content"));
    dto.setPass(mr.getParameter("pass"));
    
    // 원본 파일명과 저장된 파일 이름 설정
    // 5. 폼값을 DTO에 저장
    String fileName = mr.getFilesystemName("ofile");
    // 6. DAO를 통해 폼값을 데이터베이스에 기록 
    if (fileName != null) {
      // 7. 파일명 변셩
    	// 첨부 파일이 있을 경우 파일명 변경
    	// 새로운 파일명 생성
    	String now = new SimpleDateFormat("yyyMMdd_HmsS").format(new Date());
    	String ext = fileName.substring(fileName.lastIndexOf("."));
    	String newFileName = now + ext;
    	
    	// 8. 원래 파일명과 저장된 파일명 따로 기록, DTO에 저장
    	File oldFile = new File(saveDirectory + File.separator + fileName);
    	File newFile = new File(saveDirectory + File.separator + newFileName);
    	oldFile.renameTo(newFile);
    	
    	dto.setOfile(fileName);
    	dto.setSfile(newFileName);
    }
    
    // 9. DAO를 통해 DB에 게시 내용 저장
    MVCBoardDAO dao = new MVCBoardDAO();
    int result = dao.insertWrite(dto);
    dao.close();
    
    // 10. 성공/실패 확인
    if (result == 1) {
      resp.sendRedirect("../board/list.board");
    } else {
      resp.sendRedirect("../board/write/board");
    }
  }  
}

(3) JSFunction에 메서드 2개 추가

// Java Resources/src/utils/JSFunction.java
package utils;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspWriter;

public class JSFunction {
  ... 생략 ...
    // 메시지 알림창을 띄운 후 명시한 URL로 이동
    public static void alertLocation(HttpServletResponse resp, String msg, String url) {
      try {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        String script = "<script>"
                      + "    alert('" + msg + "');"
                      + "    location.href='" + url + "';"
                      + "</script>";
        writer.print(script);
      } catch (Exception e) {}
    }

    // 메시지 알림창을 띄운 후 이전 페이지로 돌아감
    public static void alertBack(HttpServletResponse resp, String msg) {
      try {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        String script = "<script>"
                      + "    alert('" + msg + "');"
                      + "    history.back();"
                      + "</script>";
            writer.print(script);
      } catch (Exception e) {}
    }
}

7) 글쓰기 기능 최종 실행 결과

오류 해결 : web.xml에 오타가 있어서.. WriteController의 37번째줄의 매개변수명을 정상적으로 가져오지 못한 것이었다 

java.lang.NumberFormatException: null

at java.base/java.lang.Integer.parseInt(Integer.java:614)

at java.base/java.lang.Integer.parseInt(Integer.java:770)

at mvcboard.WriteController.doPost(WriteController.java:37)

실행 성공! 

2. 상세 보기(서블릿 매핑에 어노테이션 사용)

1) 모델 작성(DAO)

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

package mvcboard;

... 임포트 생략 ...
public class MVCBoardDAO extends DBConnPool {  // 커넥션 풀 상속
  ... 생략 ...
  // 주어진 일련번호에 해당하는 게시물을 DTO에 담아 반환
  public MVCBoardDTO selectView(String idx) {
    // dto 객체 생성
    MVCBoardDTO dto = new MVCBoardDTO();
    // 쿼리문 템플릿 준비 
    String query = "SELECT * FROM mvcboard WHERE idx=?";
    
		try {
		  psmt = con.prepareStatement(query);  // 쿼리문 준비
		  psmt.setString(1, idx);  // 인파라미터 설정
		  rs = psmt.executeQuery();
		  
		  if (rs.next()) {  // 결과를 DTO 객체에 저장 
		    dto.setIdx(rs.getString(1));
		    dto.setName(rs.getString(2));
		    dto.setTitle(rs.getString(3));
		    dto.setContent(rs.getString(4));
		    dto.setPostdate(rs.getDate(5));
		    dto.setOfile(rs.getString(6));
		    dto.setSfile(rs.getString(7));
		    dto.setDowncount(rs.getInt(8));
		    dto.setPass(rs.getString(9));
		    dto.setVisitcount(rs.getInt(10));
		  }
		} catch (Exception e) {
		  System.out.println("게시물 상세보기 중 예외 발생");
		  e.printStackTrace();
		}
		return dto;  // 결과 반환
	}
  
  // 주어진 일련번호에 해당하는 게시물의 조회수를 1 증가시킴
  public void updateVisitCount(String idx) {
    String query = "UPDATE mvcboard SET"
                 + "visitcount = visitcount+1"
                 + "WHERE idx=?";
    
    try {
      psmt = con.prepareStatement(query);
      psmt.setString(1, idx);
      psmt.executeQuery();
    } catch (Exception e) {
      System.out.println("게시물 조회수 업데이트 중 예외 발생");
      e.printStackTrace();
    }
  }
}

2) 컨트롤러 작성(WebServlet 어노테이션 사용) 

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

... 임포트 생략 ...

// 어노테이션으로 요청명과 서블릿 매핑 
@WebServlet("/board/view.board")
public class ViewController extends HttpServlet{
  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
    // 게시물 불러오기 
    MVCBoardDAO dao = new MVCBoardDAO();  // DAO 객체 생성
    String idx = req.getParameter("idx");  // 게시물의 일련번호를 매개변수로 받기
    dao.updateVisitCount(idx);  // 조회수 1 증가
    MVCBoardDTO dto = dao.selectView(idx);  // 게시물 내용 가져오기(상세보기)
    dao.close();
  	
    // 줄바꿈 처리(일반 텍스트 문서의 줄바꿈문자를 HTML이 인식)
    dto.setContent(dto.getContent().replaceAll("\r\n", "<br>"));
    
    // 게시물(DTO) 저장 후 뷰로 포워드
    req.setAttribute("dto", dto);  // DTO 객체를 request 영역에 저장
    req.getRequestDispatcher("/board/View.jsp").forward(req, resp);
  }
}

3) 뷰(JSP 파일) 작성

<!-- src/main/webapp/board/View.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>
<body>
  <h2>파일 첨부형 게시판 - 상세 보기</h2>
  <table border="1" width="90%">
    <colgroup>
      <col width="15%" /> <col width="35%" />
      <col width="15%" /> <col width="*" />
    </colgroup>
    
    <!-- 게시글 정보 -->
    <tr>
      <td>번호</td><td>${ dto.idx }</td>
      <td>작성자</td><td>${ dto.name }</td>
    </tr>
    <tr>
      <td>작성일</td><td>${ dto.postdate }</td>
      <td>조회수</td><td>${ dto.visitcount }</td>
    </tr>
    <tr>
      <td>제목</td><td colspan="3">${ dto.title }</td>
    </tr>
    <tr>
      <td>내용</td><td colspan="3" height="100">${ dto.content }</td>
    </tr>
    
    <!-- 첨부파일 -->
    <tr>
      <td>첨부파일</td>
      <td>
        <c:if test="${ not empty dto.ofile }">
          ${ dto.ofile }
          <a href="../board/download.board?ofile=${ dto.ofile }&sfile=${ dto.sfile }&idx=${ dto.idx }">
            [다운로드]
          </a>       
        </c:if>
      </td>
      <td>다운로드수</td><td>${ dto.downcount }</td>
    </tr>
    
    <!-- 하단 메뉴(수정/삭제/목록으로 돌아가기 버튼) -->
    <tr>
      <td colspan="4" align="center">
        <button type="button" onclick="location.href='../board/pass.board?model=edit&idx=${param.idx}';">
          수정하기
        </button>
        <button type="button" onclick="location.href='../board/pass.board?model=delete&idx=${param.idx}';">
          삭제하기
        </button>
        <button type="button" onclick="location.href='../board/list.board';">
          목록으로 돌아가기
        </button>
      </td>
    </tr>
  </table>
</body>
</html>

4) Default.jsp 실행 후 동작 확인: 원하는 글의 제목을 클릭하면, 해당 글의 내용을 상세보기 페이지에서 볼 수 있음

 

반응형