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

JAVA/JSP 41. 모델2 방식(MVC 패턴)의 자료실형 게시판 만들기 - 프로젝트 구상, 목록 보기 구현

개발학생 2025. 5. 2. 18:14
반응형

*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. 프로젝트 구상

  • EL, JSTL, 파일 업로드, 서블릿 활용

1) 제공할 기능

  • 비회원제
    • 회원인증 없이 누구나 글 작성
    • 글쓰기 시 비밀번호 입력이 필수이며, 비밀번호를 통해 수정/삭제 가능
  • 자료실
    • 글쓰기 시 파일을 첨부할 수 있는데, 파일 첨부 시 정해진 용량 이상은 업로드 불가
    • 첨부된 파일은 다운로드 가능

2) 프로젝트 프로세스

(1) 모델2의 구조

모델2의 구조

(2) 자료실형 게시판 다이어그램

  • *.java: 컨트롤러, *.jsp: 뷰

(3) 게시판 기능별 요청명 패턴

기능 매핑 방법 요청명 컨트롤러(서블릿) 뷰(JSP) 경로
목록 보기 web.xml /board/list.board ListController /mvcboard/List.jsp
글쓰기 web.xml /board/write.board WriteController /mvcboard/Write.jsp
상세 보기 어노테이션 /board/view.board ViewController /mvcboard/View.jsp
비밀번호 검증 어노테이션 /board/pass.board PassController /mvcboard/Pass.jsp
수정 어노테이션 /board/edit.board EditController /mvcboard/Edit.jsp
삭제 어노테이션 필요 없음 PassController 필요 없음
다운로드 어노테이션 /board/download.board DownloadController 필요 없음

2. 목록 보기 구현

1) 테이블 생성

(1) 테이블 정의서

컬럼명 데이터 타입 null 허용 기본값 설명
idx number N 기본키   게시물 일련번호
name varchar2(50) N     작성자 이름
title varchar2(200) n     제목
content varchar2(2000) N     내용
postdate date N   sysdate 작성일
ofile varchar2(200)       원본 파일명
sfile varchar2(30)       저장된 파일명
downcount number N   0 다운로드 횟수
pass varchar2(50) N     비밀번호
visitcount number N   0 게시물 조회수

(2) 테이블 생성 쿼리문

create table mvcboard (
  idx number primary key,
  name varchar2(50) not null,
  title varchar2(200) not null,
  content varchar2(2000) not null,
  postdate date default sysdate not null,
  ofile varchar2(200),
  sfile varchar2(30),
  downcount number(5) default 0 not null,
  pass varchar2(50) not null,
  visitcount number default 0 not null
);

(3) 오라클 데이터베이스에 테이블을 생성하고 테스트용 데이터 추가

  • 명령 프롬프트(cmd)에서 sqlplus 명령으로 오라클 데이터베이스에 연결한 후, 사용자이름 hello 비밀번호 1234로 로그인

  • 테이블 생성 쿼리문 입력 -> desc mvcboard로 테이블 생성 확인

  • 테스트용 데이터 추가
insert into mvcboard (idx, name, title, content, pass) 
  values (seq_board_num.nextval, '감자', '자료실 제목 1', '내용1', '1234');
insert into mvcboard (idx, name, title, content, pass) 
  values (seq_board_num.nextval, '고구마', '자료실 제목 2', '내용2', '1234');
insert into mvcboard (idx, name, title, content, pass) 
  values (seq_board_num.nextval, '돼지감자', '자료실 제목 3', '내용3', '1234');
insert into mvcboard (idx, name, title, content, pass) 
  values (seq_board_num.nextval, '당근', '자료실 제목 4', '내용4', '1234');
insert into mvcboard (idx, name, title, content, pass) 
  values (seq_board_num.nextval, '양파', '자료실 제목 5', '내용5', '1234');

2) DTO 및 DAO 클래스 생성

(1) DTO 클래스

// Java Resources/src/mvcboard/MVCBoardDTO.java

package mvcboard;

public class MVCBoardDTO {
  // 1. 멤버 변수 선언
  private String idx;
  private String name;
  private String title;
  private String content;
  private java.sql.Date postdate;
  private String ofile;
  private String sfile;
  private int downcount;
  private String pass;
  private int visitcount;
	
  // 2. 게터/세터: 이클립스의 source -> Generate Getters and Setters 클릭하여 자동생성 
  public String getIdx() {
    return idx;
  }
  public void setIdx(String idx) {
    this.idx = idx;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getContent() {
    return content;
  }
  public void setContent(String content) {
    this.content = content;
  }
  public java.sql.Date getPostdate() {
    return postdate;
  }
  public void setPostdate(java.sql.Date postdate) {
    this.postdate = postdate;
  }
  public String getOfile() {
    return ofile;
  }
  public void setOfile(String ofile) {
    this.ofile = ofile;
  }
  public String getSfile() {
    return sfile;
  }
  public void setSfile(String sfile) {
    this.sfile = sfile;
  }
  public int getDowncount() {
    return downcount;
  }
  public void setDowncount(int downcount) {
    this.downcount = downcount;
  }
  public String getPass() {
    return pass;
  }
  public void setPass(String pass) {
    this.pass = pass;
  }
  public int getVisitcount() {
    return visitcount;
  }
  public void setVisitcount(int visitcount) {
    this.visitcount = visitcount;
  }
}

(2) 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 {  // 커넥션 풀 상속
  public MVCBoardDAO() {
    super();
  }
	
  // 검색 조건에 맞는 게시물 개수 반환
  public int selectCount(Map<String, Object> map) {
    int totalCount = 0;
    // 쿼리문 준비
    String query = "SELECT COUNT(*) FROM mvcboard";
    // 검색 조건이 있을 경우, WHERE 절로 추가
    if (map.get("searchWord") != null) {
      query += " WHERE " + map.get("searchField") + " "
             + "LIKE '%" + map.get("searchWord") + "%'";
    }
    try {
      stmt = con.createStatement();  // 쿼리문 생성
      rs = stmt.executeQuery(query);  // 쿼리문 실행
      rs.next();
      totalCount = rs.getInt(1);  // 검색된 게시물 개수 저장
    } catch (Exception e) {
      System.out.println("게시물 카운트 중 예외 발생");
      e.printStackTrace();
    }
		
    return totalCount;  // 게시물 개수를 서블릿으로 반환
  }
	
  // 검색 조건에 맞는 게시물 목록 반환(페이징 기능 지원)
  public List<MVCBoardDTO> selectListPage(Map<String, Object> map) {
    List<MVCBoardDTO> board = new Vector<MVCBoardDTO>();
    // 쿼리문 준비
    String query = "SELECT * FROM ( "
				 + " SELECT Tb.*, ROWNUM rNum FROM ( "
				 + "  SELECT * FROM mvcboard ";
    // 검색 조건이 있을 경우, WHERE 절로 추가
    if (map.get("searchWord") != null) {
      query += " WHERE " + map.get("searchField") + " "
             + "LIKE '%" + map.get("searchWord") + "%'";
    }
		
    query += " ORDER BY idx DESC "
           + "   ) Tb "
           + " ) "
           + " WHERE rNum BETWEEN ? AND ?";  // 게시물 구간은 인파라미터로
		
    try {
      psmt = con.prepareStatement(query);  // 동적 쿼리문 생성
      psmt.setString(1, map.get("start").toString());  // 인파라미터 설정
      psmt.setString(2, map.get("end").toString());
      rs = psmt.executeQuery();  // 쿼리문 실행
			
      // 반환된 게시물 목록을 List 컬렉션에 추가
      while (rs.next()) {
        MVCBoardDTO dto = new MVCBoardDTO();
				
        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));
				
        board.add(dto);
      }
    } catch (Exception e) {
      System.out.println("게시물 조회 중 예외 발생");
    }
    return board;  // 게시물 목록 반환
  }
}

3) 진입 화면 작성, 요청명과 서블릿 매핑 

(1) 진입 화면(뷰) jsp 파일 추가 

<!-- src/main/webapp/board/Default.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>
  <h2>파일 첨부형 게시판</h2>
  <a href="../board/list.board">게시판 목록 바로가기</a>
</body>
</html>

(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>
  ... 생략 ...
  <servlet> <!-- 서블릿 등록 -->
    <servlet-name>MVCBoardList</servlet-name>  <!-- 1. 서블릿 매핑을 위한 서블릿명 -->
    <servlet-class>mvcboard.ListController</servlet-class>  <!-- 2. 요청을 처리할 서블릿을 패키지를 포함하여 명시 -->
  </servlet>
  <servlet-mapping>  <!-- 서블릿과 요청명 매핑 -->
    <servlet-name>MVCBoardList</servlet-name>  <!-- 3. 1과 동일한 서블릿명 -->
    <url-pattern>/board/list.board</url-pattern>  <!-- 4. 컨텍스트 루트를 제외한 요청명 작성 -->
  </servlet-mapping>
  <context-param>
    <param-name>POSTS_PER_PAGE</param-name>
    <param-value>10</param-value>
  </context-param>
  <context-param>
    <param-name>POSTS_PER_BLOCK</param-name>
    <param-value>5</param-value>
  </context-param>
</web-app>

4) List 컨트롤러(서블릿) 작성, BoardPage.java 작성 

// Java Resources/src/mvcboard/ListController.java

package mvcboard;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

// 1.
public class ListController extends HttpServlet {
	
  // 2.
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
  	// 3. DAO 생성
  	MVCBoardDAO dao = new MVCBoardDAO();
  		
  	// 4. 뷰에 전달할 매개변수 저장용 맵 생성
  	Map<String, Object> map = new HashMap<String, Object>();
  		
  	String searchField = req.getParameter("searchField");
  	String searchWord = req.getParameter("searchWord");
  		
  	// 5. 쿼리스트링으로 전달받은 매개변수 중 검색어가 있다면 map에 저장
  	if(searchWord != null) {
  		map.put("searchField", searchField);
  		map.put("searchWord", searchWord);
  	}
  	// 6. 게시물 개수
  	int totalCount = dao.selectCount(map);
  		
  	// 7. 페이지 처리 시작
  	ServletContext application = getServletContext();
  	int pageSize = Integer.parseInt(application.getInitParameter("POSTS_PER_PAGE"));
  	int blockPage = Integer.parseInt(application.getInitParameter("POSTS_PER_BLOCK"));
  		
  	// 8. 현재 페이지 확인
  	int pageNum = 1;  // 기본값
  	String pageTemp = req.getParameter("pageNum");
  	if (pageTemp != null && !pageTemp.equals("")) {
  		pageNum = Integer.parseInt(pageTemp);  // 요청받은 페이지로 수정
  	}
  	
  	// 9. 목록에 출력할 게시물 범위 계산
  	int start = (pageNum - 1) * pageSize + 1;  // 첫 게시물 번호
  	int end = pageNum * pageSize;  // 마지막 게시물 번호
  	map.put("start", start);
  	map.put("end", end);  // 페이지 처리 끝
  	
  	List<MVCBoardDTO> boardLists = dao.selectListPage(map);
  	// 10. 게시물 목록 받기
  	dao.close();	// DB 연결 닫기
  	
  	// 11. 
  	// 뷰에 전달할 매개변수 추가
  	String pagingImg = BoardPage.pagingStr(totalCount, pageSize, blockPage, pageNum, "../board/list.board");
  	// 바로가기 영역 HTMl 문자열
  	map.put("pagingImg", pagingImg);
  	map.put("totalCount", totalCount);
  	map.put("pageSize", pageSize);
  	map.put("pageNum", pageNum);
  	
  	// 12. 전달할 데이터를 request 영역에 저장 후 List.jsp로 포워드
  	// 13.
  	req.setAttribute("boardLists", boardLists);
  	req.setAttribute("map", map);
  	req.getRequestDispatcher("/board/List.jsp").forward(req, resp);
  }
}
// Java Resources/src/mvcboard/BoardPage.java

package mvcboard;


public class BoardPage {
  public static String pagingStr(int totalCount, int pageSize, int blockPage,
      int pageNum, String reqUrl) {
    String pagingStr = "";

    // 전체 페이지 수 계산
    int totalPages = (int) (Math.ceil(((double) totalCount / pageSize)));    //1.
		
    // '이전 페이지 블록 바로가기' 출력
    int pageTemp = (((pageNum -1) / blockPage) * blockPage) + 1;
    if (pageTemp != 1) {    //2.
      pagingStr += "<a href='" + reqUrl + "?pageNum=1'>[첫 페이지]</a>";    //3.
      pagingStr += "&nbsp;";
      pagingStr += "<a herf='" + reqUrl + "?pageNum=" + (pageTemp -1)
                   +"'>[이전 블록]</a>";    //4.
    }

    // 각 페이지 번호 출력
    int blockCount = 1;
    while (blockCount <= blockPage && pageTemp <= totalPages) {
      if (pageTemp == pageNum) {    //5.
        //현재 페이지는 링크를 걸지 않음
        pagingStr += "&nbsp;" + pageTemp + "&nbsp;";
      } else { 
        pagingStr += "&nbsp;<a href='" + reqUrl + "?pageNum=" + pageTemp
                    + "'>" + pageTemp + "</a>&nbsp;";    //6.
      }
      pageTemp++;    //7.
      blockCount++;
    }
		
    // '다음 페이지 블록 바로가기' 출력
    if (pageTemp <= totalPages) {    //8.
      pagingStr += "<a href'" + reqUrl + "?pageNum=" + pageTemp
                   + "'>[다음 블록]</a>";    //9.
      pagingStr += "&nbsp;";
      pagingStr += "<a href='" + reqUrl + "?pageNum=" + totalPages
                   + "'>[마지막 페이지]</a>";    //10.
     }

     return pagingStr;
  }
}

5) 뷰(JSP) 만들기

<!-- src/main/webapp/board/List.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>
  
  <!-- 검색 폼: 검색어는 ListController로 전송된 후 DAO 클래스 메서드들의 인수로 전달됨 -->
  <form method="get">
  	<table border="1" width="90%">
  	  <tr>
  	    <td align="center">
  	      <select name="searchField">
  	        <option value="title">제목</option>
  	        <option value="content">내용</option>
  	      </select>
  	      <input type="text" name="searchWord" />
  	      <input type="submit" value="검색하기" />
  	    </td>
  	  </tr>
  	</table>
  </form>
  
  <!-- 목록 테이블 -->
  <table border="1" width="90%">
    <tr>
      <th width="10%">번호</th>
      <th width="*">제목</th>
      <th width="15%">작성자</th>
      <th width="10%">조회수</th>
      <th width="15%">작성일</th>
      <th width="8%">첨부</th>
    </tr>
  
<c:choose>    
    <c:when test="${ empty boardLists }">  <!-- 게시물이 없을 때: EL의 empty 연산자로 게시물이 있는지 확인 -->
        <tr>
            <td colspan="6" align="center">
                등록된 게시물이 없습니다^^*
            </td>
        </tr>
    </c:when>
    <c:otherwise>  <!-- 게시물이 있을 때: c:forEach 태그로 반복 출력 -->
        <c:forEach items="${ boardLists }" var="row" varStatus="loop">   <!-- varStatus로 게시물의 가상번호 계산 --> 
        <tr align="center">
            <td>  <!-- 번호 -->
                ${ map.totalCount - (((map.pageNum-1) * map.pageSize) + loop.index)}   
            </td>
            <td align="left">  <!-- 제목(상세보기 페이지 링크) -->
                <a href="../board/view.board?idx=${ row.idx }">${ row.title }</a> 
            </td> 
            <td>${ row.name }</td>  <!-- 작성자 -->
            <td>${ row.visitcount }</td>  <!-- 조회수 -->
            <td>${ row.postdate }</td>  <!-- 작성일 -->
            <td>  <!-- 첨부 파일(다운로드 링크) -->
              <c:if test="${ not empty row.ofile }">
                <a href="../board/download.board?ofile=${ row.ofile }&sfile=${ row.sfile }&idx=${ row.idx }">[Down]</a>
              </c:if>
            </td>
        </tr>
        </c:forEach>        
    </c:otherwise>    
</c:choose>
    </table>

    <!-- 하단 메뉴(페이지 번호 바로가기, 글쓰기) -->
    <table border="1" width="90%">
        <tr align="center">
            <td>
                ${ map.pagingImg }
            </td>
            <td width="100"><button type="button"
                onclick="location.href='../board/write.board';">글쓰기</button></td>
        </tr>
    </table>
</body>
</html>

 

6) Default.jsp 실행 결과 - 검색 기능까지 잘 동작함 

반응형