JSP Model 2 게시판 프로그램 만들기

게시판 프로그램

- 댓글게시판 + 자료실 게시판

 

게시판 프로그램 : 주요 기능 소개

1. Connection Pool

2. 댓글 기능

3. 자료실 – 첨부파일 업로드, 다운로드 : cos 라이브러리 사용

- Model 1, Model 2 에선 주로 cos 라이브러리 사용, Spring 에서는 다른 라이브러리 사용

4. request 객체 공유 설정

- 게시판에서는 request 객체로 공유하는 경우가 많다

5. Controller 클래스 : Java Servlet

6. Model = Service + DAO Service, DTO, DAO 클래스

7. View (화면 인터페이스) : EL , JSTL 사용

 

게시판 프로그램 : 프로그램 구조 설명, 구조도

- Model 2 에서는 DAO, DTO 가 다른 패키지에 있다

- 같은 기능을 하는 클래스들끼리 묶어서 패키지 안에 저장

- controller 패키지 안에 Controller 클래스, dao 패키지 안에 DAO 클래스, model 패키지 안에 DTO 클래스

- service 패키지 안에 Service 클래스들, board 안에 View 페이지들

 

게시판 프로그램 : 프로젝트 생성

- 위의 구조도처럼 만들기 위해 model2board 라는 프로젝트부터 생성

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

 

게시판 프로그램 : 기초 파일 가져오기

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

 

게시판 프로그램 : 몇가지 환경 구축

1. WebContent/WEB-INF/lib 폴더 하위에 라이브러리 3개 넣기

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

2) JSTL 을 사용하기 위한 라이브러리 jstl-1.2.jar 를 WEB-INF/lib 폴더 안으로 가져오기

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

2. board 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

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

 

게시판 프로그램 : 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)          
-->

- 테이블을 아직 생성하지 않았지만 계정이 설정되어있으므로 Connection Pool 테스트가 가능하다

 

- 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();
 	}
%>

- 서버를 한번 중지시켰다가 dbcpAPITest.jsp 실행시

- 정상적으로 Connection Pool 에서 Connection을 구해온다

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

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

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

 

 

게시판 프로그램 : 첨부파일을 저장할 폴더 생성

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

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

 

게시판 프로그램 : 관련 컬럼

- board_num : 게시판에서는 글 번호가 primary key 가 된다, 글 번호는 sequence 로 입력한다

- board_pass : 글 작성시 작성한 비밀번호를 수정/삭제 시 사용하도록 하기 위한 비밀번호

- board_file : 첨부파일명

- board_re_ref : 글 그룹 번호, 원문은 글번호 num과 같은 숫자가 들어가고, 댓글은 부모와 같은 숫자가 들어간다

- board_re_lev : 댓글 깊이, 원문은 0 이 저장, 1단계 댓글은 1 저장, 2단계 댓글은 2 저장

- board_re_seq : 댓글을 출력 순서 제어, 원문은 0 으로 고정, 나머지는 오름차순 정렬되며 계속해서 바뀌는 컬럼

 

게시판 프로그램 : 테이블 및 시퀀스 생성

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

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

create table model2(
	board_num number primary key,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50), -- 첨부 파일명
	board_re_ref number, -- 글 그룹번호
	board_re_lev number, -- 댓글 깊이 : 원문(0), 1, 2...
	board_re_seq number, -- 댓글 출력 순서 : 원문(0) 오름차순 정렬
	board_readcount number,
	board_date timestamp );
    
create sequence model2_seq
	start with 1
	increment by 1
	nocache;

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

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

- totoro 계정으로 커넥션을 연결하고

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

- model2 테이블과 model2_seq 테이블 생성 확인

- 생성확인 완료

 

게시판 프로그램 : DAO 와 DTO 클래스 만들기

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

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

- 해당 클래스를 사용할때 반드시 import 를 해야하고, 접근제어자는 public 이어야한다

- 나중에 회원관리, 게시판이 같은 프로젝트 안에 들어갈땐, 같은 기능을 하는 파일끼리 같은 폴더에 넣음

 

- 프로그램 구조도 대로 src안에 model 패키지를 만들고 그 안에 DTO 클래스를 만든다

- 프로그램 구조도 대로 src안에 dao 패키지를 만들고 그 안에 DAO 클래스를 만든다

 

게시판 프로그램 : DTO 클래스 작성

- 테이블 model2 의 create 문에서 가져와서 DTO 클래스에 복붙 후 수정

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

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

+ java.sql 의 Timestamp import

- 이후 getter / setter 메소드 추가

 

- DTO 클래스 완성 코드

- BoardBean.java

// DTO (Data Transfer Object)
package model;

import java.sql.Timestamp;

public class BoardBean {
	private int board_num;
	private String board_name;
	private String board_pass;
	private String board_subject;
	private String board_content;
	private String board_file;
	private int board_re_ref;
	private int board_re_lev;
	private int board_re_seq;
	private int board_readcount;
	private Timestamp board_date;
	
	public int getBoard_num() {
		return board_num;
	}
	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}
	public String getBoard_name() {
		return board_name;
	}
	public void setBoard_name(String board_name) {
		this.board_name = board_name;
	}
	public String getBoard_pass() {
		return board_pass;
	}
	public void setBoard_pass(String board_pass) {
		this.board_pass = board_pass;
	}
	public String getBoard_subject() {
		return board_subject;
	}
	public void setBoard_subject(String board_subject) {
		this.board_subject = board_subject;
	}
	public String getBoard_content() {
		return board_content;
	}
	public void setBoard_content(String board_content) {
		this.board_content = board_content;
	}
	public String getBoard_file() {
		return board_file;
	}
	public void setBoard_file(String board_file) {
		this.board_file = board_file;
	}
	public int getBoard_re_ref() {
		return board_re_ref;
	}
	public void setBoard_re_ref(int board_re_ref) {
		this.board_re_ref = board_re_ref;
	}
	public int getBoard_re_lev() {
		return board_re_lev;
	}
	public void setBoard_re_lev(int board_re_lev) {
		this.board_re_lev = board_re_lev;
	}
	public int getBoard_re_seq() {
		return board_re_seq;
	}
	public void setBoard_re_seq(int board_re_seq) {
		this.board_re_seq = board_re_seq;
	}
	public int getBoard_readcount() {
		return board_readcount;
	}
	public void setBoard_readcount(int board_readcount) {
		this.board_readcount = board_readcount;
	}
	public Timestamp getBoard_date() {
		return board_date;
	}
	public void setBoard_date(Timestamp board_date) {
		this.board_date = board_date;
	}
	
}

 

게시판 프로그램 : 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 클래스 BoardDAO.java 코드

// DAO (Data Access Object)
package dao;

import java.sql.Connection;

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

public class BoardDAO {
	
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDAO instance = new BoardDAO();
	
	public static BoardDAO 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에 작성

 

게시판 프로그램 : Controller 클래스

- Java Servlet 클래스로 만든다

- 모든 요청의 진입점

- src에서 오른마우스를 클릭해서 Servlet 선택

- controller 패키지, BoardFrontController 클래스 를 만든다

- Controller 클래스 생성 완료

 

- BoardFrontController.java (수정 전 1 , 기본적인 것만 작성했음)

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

 

- 오류 생겼을때 어느정도까지 실행되었는지 확인하기 위해서 System.out.println(); 으로 브라우저에 출력

- doGet() 과 doPost() 에 같은 내용을 써야한다, 요청 방식에 따라 서로 다른 일을 하는게 더 이상함

- doGet() 과 doPost() 에서 처리할 내용은 동일해야하므로 따로 공통적인 작업을 처리하는 메소드 doProcess() 를 만듬

- 그 메소드 doProcess() 를 doGet(), doPost() 에서 호출하도록 함

- doGet(), doPost() 는 자동으로 호출되는 메소드이므로 doGet(), doPost() 의 매개변수 request, response 를 doProcess() 호출시 전달

- doProcess() 안의 내용은 아래에

 

WebServlet 어노테이션 이름값으로 "*.do" 로 임의로 설정

@WebServlet("*.do")

- "*.do" 는 do 확장자로 요청하는 모든 요청을 받는다는 의미

- "*.do" 는 앞쪽의 파일명은 어떤 이름이어도 상관없고, 확장자가 do 인 모든 요청을 받는다는 의미

- 이 확장자는 임의로 설정한다, 주로 회사명

 

- @WebServlet("/") 로 하면 아무 이름으로 찾아가도 무조건 여기로 찾아온다

- 지금은 특정 확장자로 요청할때만 찾아오도록 확장자를 사용하자

+ 아파치 톰캣 버전 6 이하에는 @WebServlet 어노테이션 대신 web.xml 에 관련 내용들이 들어갔었다

+ Spring 프로젝트에서는 어노테이션 대신 과거 방식 처럼 Controller 클래스 이름과 위치, Servlet 매핑을 잡는 내용을 web.xml 안에서 설정함

+ Spring 에선 web.xml 안의 servlet  class (controller class) 관련 내용, 매핑 잡는 내용(요청 이름값 등) 들이 들어가있다

 

doProcess() 안의 내용 (BoardFrontController.java 부분)

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end

- request 객체에서 제공되는 6개의 메소드 중 getRequestURI() 와 getContextPath() 를 쓰고 있다

- Controller 클래스는 요청을 어떤 서비스 클래스로 넘겨줄지 결정함

- getRequestURI() : 요청 URI 를 구해줌 ex) /model2board/BoardAddAction.do

- getContextPath() : 현재 프로젝트 명을 구해줌 ex) /model2board

- substring() 으로 contextPath 변수(현재 프로젝트명) 의 길이 숫자부터 시작해서 추출

- 추출시 요청 URI 만 남게되고, 이 요청 URI 가 command 변수에 저장됨 ex) /BoardAddAction.do

 

Controller 클래스에서 맞는 Service 클래스로 요청을 넘기는 방법 (BoardFrontController.java 부분)

- /model2board 의 길이는 12, /model2board/BoardAddAction.do 에서 0번 인덱스부터 시작하므로 12번 인덱스는 /, 즉 /BoardAddAction.do 가 command 가 된다

 

요청을 보내는 방법

- 게시판에 원문 글을 작성하는 양식 WebContent/board/qna_board_write.jsp 파일을 보자

- qna_board_write.jsp 에서 form 의 action 값을 확인해보자

- 요청이름 확장자가 .do 이므로 Controller 클래스로 찾아간다

+ 요청 이름값과 Service 클래스명을 일치시킬 것 ex) 원문 글 작성 Service 클래스는 BoardAddAction.java 로 만들 것

 

- qna_board_write.jsp 를 실행시켜서 아무 값이나 입력하고 '등록' 을 누른 후 콘솔창을 보자

- 콘솔창 확인

- 요청이름값이 command 변수에 저장됨

 

만약 qna_board_write.jsp 에서 form 태그 action에 이렇게 패키지명을 쓰지 않고 ./ 로 쓰면

- requestURI 에 다른 하위폴더도 나오게 되어서 경로가 잘 맞지 안게 됨

 

게시판 프로그램 : Service 클래스의 부모 인터페이스

- Service 클래스를 통일성 있게 설계하기 위해 부모가 될 인터페이스 Action 먼저 만들기

- 인터페이스 Action 을 만들고 안에는 추상메소드 execute() 만들기

- Action.java

package service;

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

public interface Action {
	
	// 추상 메소드
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

- 리턴자료형은 ActionForward 로 한다

- Controller 클래스의 doProcess() 의 매개변수 request, response 를 그대로 execute() 의 매개변수에 복사

- 추상메소드 execute() 는 형식만 있다

 

게시판 프로그램 : ActionForward 클래스

- service 패키지 안에 ActionForward 클래스 만들기

- ActionForward.java

package service;

public class ActionForward {
	
	private boolean redirect;	// 포워딩 방식 설정
	private String path;		// 포워딩 페이지명 설정
	
	public boolean isRedirect() {
		return redirect;
	}
	public void setRedirect(boolean redirect) {
		this.redirect = redirect;
	}
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	
}

- 포워딩 방식과 포워딩 페이지명을 저장하는 클래스

1. 포워딩 방식을 결정할 변수 redirect 정의,  dispatcher, redirect 방식 중 어느 것으로 할지 true, false 로 결정

+ request 객체로 공유가 되었을때는 반드시 dispatcher 방식으로 포워딩되어야 뷰페이지에 출력 가능

2. 포워딩할 페이지명이 저장될 변수 path 를 정의

- 두 변수는 private 으로 설정

3. getter, setter 메소드 만들기

- Service 클래스에서 값을 처리하고 View 페이지로 가기 전에 이 ActionForward 로 포워딩 방식, 포워딩 페이지명을 결정

- setter 메소드는 Service 클래스 하단에서 호출해서 설정하고, getter 메소드는 Controller 하단에서 포워딩 할때 호출한다

 

- 다시 Controller 클래스로 돌아와서 Service 클래스로 넘겨주는 작업 및 포워딩 작업 코드를 쓰자

- BoardFrontController.java  (수정 전 2)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction")) {
			
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

- 모두 doProcess() 안에서 작성한다

- service 패키지 안의 Action 인터페이스 객체 action 를 만들고 null 로 초기화

- service 패키지 안의 ActionForward 클래스 객체 forward 를 만들고 null 로 초기화

- 원문 글 작성 요청이름값인 "/BoardAddAction.do" 일때의 처리를 해줌. Service 클래스 BoardAddAction.java 로 넘기기

- 마지막 부분에서 포워딩 처리를 한다

- forward != null 로 인해 DAO -> Service -> Controller 로 돌아올때만 여기서 포워딩을 해줌

 

포워딩 처리 (BoardFrontController.java 부분)

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Controller 클래스에서는 포워딩 처리도 한다

- forward 객체는 한번 Service 클래스로 갔다오면 null 이 아니게 됨

- forward.isRedirect() 는 true, false 를 돌려줌, true면 redirect 방식 포워딩, false 면 diepacher 방식으로 포워딩 하기로 함

- getPath() 메소드로 포워딩할 페이지 불러오기

 


게시판 프로그램 : 원문 글 작성

- 이제 잠시 Controller 클래스는 두고, 원문 글 작성 기능을 먼저 구현하자

- 먼저 원문 글 작성 폼 qna_board_write.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>MVC 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">MVC 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">파일 첨부</div>
		</td>
		<td>
			<input name="board_file" type="file"/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="등록">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

 

원문 글 작성 폼 form 태그 보기 (qna_board_write.jsp)

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">

- 개발환경이 아닐땐 프로젝트 명이 빠지므로, 프로젝트 명을 명시하지 말고 request.getContextPath() 메소드 사용하기

- 첨부파일을 포함한 데이터 전송시에는 method 는 post 방식으로만 가능하다

- 첨부파일을 전송하기 위해 반드시 enctype="multipart/form-data" 가 필요하다

- action 을 .do 확장자로 작성했으므로 Controller 클래스로 잘 찾아감

 

 

테이블 컬럼과 폼에서 전달되는 값 비교

- board_name, board_pass, board_subject, board_content, board_file 값만 폼에서 넘어간다

- model2 테이블 컬럼에서 댓글처리 3개 컬럼, 조회수 컬럼, 작성시간 컬럼은 form 에서 넘어가지 않는다

- 원문 글이므로 board_re_ref 컬럼은 board_num 과 똑같이 sequence 로 들어감, board_re_lev 컬럼은 0이 들어감

+ 가입 양식의 name 값, DTO 의 프로퍼티명, 테이블의 컬럼명을 일치시켜야편함, MyBatis 사용시 반드시 일치시켜야함

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (미완성, 연결 확인만 하기)

- 실제 원문 글 작성 요청을 처리하는 BoardAddAction.java 파일을 만들자

- 원문 글 작성 폼의 요청이름값과 동일하게 설정

- service 패키지에 BoardAddAction.java 클래스 생성

- BoardAddAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardAddAction.java 클래스가 있다)

package service;

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

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 원문 글 작성 Service 클래스 BoardAddAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardAddAction.do 로 요청이 올때의 경우를 처리

		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

- Controller 클래스는 어떤 Service 클래스로 넘겨줄건지 결정

- command 값, 즉 요청 이름값이 "/BoardAddAction" 면 회원가입을 처리하는 BoardAddAction 클래스로 객체 action 생성

- 왼쪽은 부모 인터페이스, 오른쪽은 상속받는 구현 클래스가 왔다, 업캐스팅을 하고 있음

- action 객체를 통해 BoardAddAction () 클래스의 오버라이딩된 메소드인 execute() 를 실행하고 ActionForward 객체 forward 로 리턴받음

- 해당 클래스 BoardAddAction 의 객체를 만들고 execute() 를 실행하므로써 그 Service 클래스 BoardAddAction 로 가는 것임

+ 메소드 오버라이딩이 되면 오버라이딩 된 메소드만 실행된다

- execute() 를 호출하며 매개변수로는 doGet(), doPost() 의 매개변수인 request, response 를 전달함

+ try-catch 예외처리 해야한다

- DB 연동을 할 때만 Service 클래스로 넘어가고, DB 연동을 하지 않으면 바로 포워딩 객체 생성 후 포워딩 경로, 방식 설정 후 아래의 포워딩 코드로 포워딩하면 된다

* BoardAddAction 클래스는 원문 글 작성 Service 클래스이다.

 

- 이제 View 페이지, Service 클래스, Controller 에서 연결까지 했으니 원문 글 작성 폼인 qna_board_write.jsp 실행

- '등록' 을 클릭하고 콘솔창을 보자 

- BoardAddAction 이 출력되는 것을 보아, 폼 -> Controller -> Service 까지 왔음을 확인 가능

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (이어서)

- BoardAddAction.java (수정 후)

package service;

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

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import dao.BoardDAO;
import model.BoardBean;

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결
		
		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");
		
		return forward;
	}

}

- 첨부파일이 원문 글 작성 폼 에서 넘어왔으므로 request 객체로 getParamter() 해서 값을 받을 수 없다

- 넘어온 첨부파일을 받아서 서버로 전송해야한다

 

첨부파일을 서버로 업로드 (BoardAddAction.java 부분)

		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결

1. 실제 업로드할 위치 경로를 getRealPath() 로 구해서 path 변수에 저장

- 폴더 boardupload 에 저장할 것

2. 첨부파일의 크기 구하기 (단위 : Byte)

3. MultipartRequest 객체를 생성하면 자동으로 업로드 된다

- DefaultFileRenamePolicy 객체를 생성해서 중복문제도 해결해줌

- cos 라이브러리에서 지원되는 2가지 클래스이다

- 한글 인코딩을 여기서 했으므로 따로 할 필요 없다

- Model 1 자료실 게시판, 첨부파일 업로드 : https://laker99.tistory.com/120?category=1080281 

 

전달된 나머지 값 받기 (BoardAddAction.java 부분)

		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));

- 첨부파일을 제외한 나머지 값들은 request 객체로 값을 받을 수 없다, multi 객체로 받아야함

- java 파일이거나 첨부파일 전송이 포함되어있을때는 useBean, setProperty action tag 사용 불가능

- 일일히 setter 메소드로 객체 board에넣어주기

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

- 그 중 여기선 실제 서버에 저장된 파일명을 getFilesystemName("board_file") 메소드로 가져와서 board 객체에 저장

- 매개변수에 작성하는 "board_file" 은 첨부파일 업로드 양식의 name 값이다

 

DB 의 메소드 호출해서 DB 와 연동 (BoardAddAction.java 부분)

		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");

- 그 후 DAO 객체를 생성하고 DAO 의 insert() 메소드로 사용자가 입력한 값들을 DB에 삽입함

- 글 작성 이후 목록페이지

 

포워딩 방식, 포워딩 페이지 설정 (BoardAddAction.java 부분)

		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");

- 목록페이지로 가는 요청이 아니라 DB에서 목록을 가져오는 요청이다

- 목록을 가져오기 위한 Service 클래스와 목록을 출력하는 View 페이지가 따로 있다

- View 페이지로 가는게 아닌 목록을 가져오기 위한 요청인 "/BoardListAction.do" 로 목록을 가져와야함

- 글 작성 성공 후 목록페이지로 돌아가야한다,그냥 목록페이지로 가면 아무것도 없음

- 목록을 끄집어내는 요청 "/BoardAddAction" 을 먼저 해야한다, 다시 Controller 클래스로 감

 

- 이제 Service -> DAO 로 가야한다

- 작성된 원문글을 DB에 삽입하는 메소드 insert() 를 DAO 클래스에 작성

 

DAO 클래스 원문 글 작성 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 insert() 부분 코드만

	// 글 작성 : 원문작성
	public int insert(BoardBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into model2 values(model2_seq.nextval,?,?,?,?,?,";
			sql += "model2_seq.nextval,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_pass());
			pstmt.setString(3, board.getBoard_subject());
			pstmt.setString(4, board.getBoard_content());
			pstmt.setString(5, board.getBoard_file());
			pstmt.setInt(6, 0);	// board_re_lev
			pstmt.setInt(7, 0); // board_re_seq
			pstmt.setInt(8, 0); // board_readcount
			
			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;
	}

- 사용자가 원문 글 작성폼에 입력한 정보를 저장하고 있는 DTO 객체 board 가 매개변수로 넘어옴

- board_re_ref 값은 board_num 과 같은값이 들어가야하므로 같은 시퀀스 mode2_seq 사용하면 같은 값 들어감

- 원문이므로 board_re_lev, board_re_seq, readcount 는 0 이 들어갸아한다

- insert 수행 후 Service 클래스인 BoardAddAction.java 로 돌아간다

 

- 이제 DB와 연동이 끝났으니 원문 글 작성 폼 qna_board_write.jsp 를 실행해서 글을 작성해보자

- 아직 뒤는 구현하지 않았으므로 나오지 않음, 콘솔창 보기

- 글 작성 성공 메세지가 나타났으므로 insert 가 잘 되었음을 알 수 있다

- path : 에 찍혀진 경로가 실제 첨부파일이 업로드되는 폴더이다

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

- 탐색기에 이 경로를 쳐보자

- 사용자가 첨부한 첨부파일도 잘 업로드 되었다

+ 빨간 선 아래는 BoardAddAction.java 에서 요청한 "/BoardListAction.do" 이다,

- dispatcher 방식 포워딩 시 URL 주소가 바뀌지 않으므로 post 방식으로 찍혀나오므로 빨간 선 아래도 post 로 나왔음


게시판 프로그램 : 목록 가져오기

흐름

- BoardAddAction.java(글 작성) 에서 "/BoardListAction.do" 로 포워딩 페이지를 설정했다, Controller 클래스로 돌아감

- BoardFrontController.java 부분

- 아래 포워딩 처리 코드에서 "/BoardListAction.do" 로 포워딩하므로 다시 Controller 클래스의 WebContent 어노테이션으로 찾아감

- 그리고 다시 Controller 에서 command.equals("/BoardListAction.do") 인 조건문으로 와서 BoardListAction.java Service 클래스로 찾아감

- 요청이 /BoardListAction.do 로 온 것을 처리하는 코드를 추가 한 후이다.

 

Model 1 vs Model 2 목록 페이지

- Model 1 에선 list.jsp 파일에서 모두 작업했다

- Model 2 에선 목록을 가져오는 파일 (Service 클래스) 과 화면에 뿌려주는 파일 (JSP 파일) 이 따로 있다

 

- service 클래스 안에 BoardListAction.java 생성

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (미완성, 연결 확인만 하기)

- service 클래스 안에 BoardListAction.java 생성

- BoardListAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardListAction.java 클래스가 있다)

package service;

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

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 목록 가져오기 Service 클래스 BoardListAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardListAction.do 로 요청이 올때의 경우를 처리

		// 글 목록
		} else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (이어서)

- 페이징 처리를 해서 목록을 가져와야한다, 가져온 목록을 request 객체로 공유godigksek

- View 에서 그 공유값을 가져와서 목록을 뿌린다 (화면에 출력)

- BoardListAction.java (수정 후)

package service;

import java.util.List;

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

import dao.BoardDAO;
import model.BoardBean;

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		int page = 1; 	// 현재 페이지 번호
		int limit = 10;	// 한 페이지에 출력할 데이터 개수
		
		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}
		
		// page=1 : startRow=1, endRow=10
		// page=2 : startRow=11, endRow=20
		// page=3 : startRow=21, endRow=30
		int startRow = (page - 1) * limit + 1;
		int endRow = page * limit;
		
		BoardDAO dao = BoardDAO.getInstance();
		int listcount = dao.getCount(); // 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		List<BoardBean> boardlist = dao.getList(startRow, endRow);
		System.out.println("boardlist : " + boardlist);
		
		// 총 페이지 수
		int pageCount = listcount/limit + ((listcount % limit == 0) ? 0 : 1);
		
		int startPage = ((page - 1) / 10) * limit + 1;	// 1, 11, 21
		int endPage = startPage + 10 - 1;			// 10, 20, 30
		
		if(endPage > pageCount) endPage = pageCount;
		
		// 공유 설정
		request.setAttribute("page", page);
		request.setAttribute("listcount", listcount);
		request.setAttribute("boardlist", boardlist);
		request.setAttribute("pageCount", pageCount);
		request.setAttribute("startPage", startPage);
		request.setAttribute("endPage", endPage);
		
		// request 객체로 공유한 경우에는 dispatcher 방식으로 포워딩되어야,
		// view 페이지에서 공유한 값에 접근 가능
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_list.jsp");
		return forward;
	}

}

1. 기본변수 page : 사용자가 클릭함으로서 넘어온 페이지값이 있다면 그 페이지값을 page 변수에 저장, 없다면 1 이 저장됨

ex) 하단의 페이지 들 중 3 페이지를 클릭하면 다시 BoardListAction.java 로 오게되어 page 변수에 3이 들어간다

2. 기본변수 limit : 한 화면에 출력할 데이터 개수, 현재 10개로 설정

3. 파생변수 startRow / endRow : 목록을 limit (10) 개씩 잘라주기 위한 변수, 페이지 번호에 따라 결정됨

ex) page가 3이면, startRow 는 21, endRow 는 30 으로서 21 ~ 30번쨰 글을 가져옴

4. 기본변수 listcount : 총 데이터 개수, DB와 연동하여 getCount() 메소드로 가져옴

5. 다음에는 DB와 연동하여 실제로 목록을 자르고 목록을 List 로 구해오는 getList() 메소드를 호출하고 결과를 리스트 boardlist 로 받자

- 나중에 출력시 DTO 객체 (주솟값) 가 콘솔창에 찍혀나온다

- DAO 에서 다시 여기로 돌아와서 돌려받은 리스트 boardlist 에 저장된 데이터를 출력해야한다

6. 파생변수 pageCount : 총 페이지 수를 구해서 저장, 총 데이터개수을 limit 으로 나누고, 나누어 떨어지지 않는다면 1을 더함

7. 파생변수 startPage / endPage : 블럭의 시작 페이지와 끝페이지

- 여기서 블럭은 페이지 10개를 출력하도록 했으므로 startPage, endPage 를 만들때 10 으로 나누고 더하고 있다

ex) 두번째 블럭은 시작페이지가 11 페이지, 끝 페이지가 20 페이지, 세번째 블럭은 시작페이지가 21 페이지

- 가장 마지막 블럭에서는 무조건 10개의 페이지 출력을 하면 안되므로 마지막 페이지를 총 페이지수로 설정함

		if(endPage > pageCount) endPage = pageCount;

8. 마지막 파생변수인 화면 출력 번호는 목록을 출력하는 View 페이지에서 만들기

- 블럭에 있는 1 2 3 ... 10 처럼 페이지를 클릭할 수 있는 것은 View 페이지에서 만들 것

9 . View 페이지에서 목록을 출력해야하므로 여기서 모든 기본변수, 파생변수, 검색결과(데이터)를 저장한 리스트를 request 객체로 공유설정해야함

- 공유되는 값의 형태를 잘 봐야함

10. ActionForward 객체 forward 를 생성 하고 포워딩 방식과 포워딩 페이지를 설정해줌

- request 공유설정 했으므로 반드시 dispatcher 방식으로 포워딩한다

- 목록(게시판) View 페이지인 qna_board_list.jsp 로 포워딩 한다

- 즉 여기 BoardListAction Service 클래스에서 DB에서 목록을 먼저 가져온 후 공유가 선행되어야 한다

- 그 다음에 목록 View 페이지로 포워딩해야 공유된 값을 가져와서 View 에서 출력 가능

11. 다음으로는 Controller 클래스로 돌아가서 forward 객체를 받아서 Controller 하단 포워딩 코드로 qna_board_list.jsp 로포워딩한다

 

값의 형태에 따라 View 페이지에서 출력하는 방법이 다르다

- page, listcount, pageCount, startPage, endPage 는 기본자료형 공유값이므로 바로 ${네임값} 으로 출력 가능

- boardlist 는 리스트이므로 forEach 의 items 안에 들어간다

 

- Model 1 게시판 목록 가져오기 부분 : https://laker99.tistory.com/123?category=1080281 

* DAO 클래스 getCount() 메소드 작성은 아래에

* DAO 클래스 getList() 메소드 작성은 아래에

 

DAO 클래스 총 데이터 개수 구하는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getCount() 부분 코드만

	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select count(*) from model2";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			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;
	}

- select 문이므로 ResultSet 객체 rs 가 필요

- 그룹함수 count(*) 를 사용해서 총 데이터 개수를 구한 후 그걸 result 변수에 저장해서 돌려준다

 

DAO 클래스 목록을 자르고 목록을 구해주는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getList() 부분 코드만

	// 글 목록
	public List<BoardBean> getList(int start, int end) {
		List<BoardBean> list = new ArrayList<BoardBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from ( select rownum rnum, board.* from ";
			sql += " (select * from model2 order by board_re_ref desc, ";
			sql += " board_re_seq 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()) {
				BoardBean board = new BoardBean();
				
				board.setBoard_num(rs.getInt("board_num"));
				board.setBoard_name(rs.getString("board_name"));
				board.setBoard_pass(rs.getString("board_pass"));
				board.setBoard_subject(rs.getString("board_subject"));
				board.setBoard_content(rs.getString("board_content"));
				board.setBoard_file(rs.getString("board_file"));
				board.setBoard_re_ref(rs.getInt("board_re_ref"));
				board.setBoard_re_lev(rs.getInt("board_re_lev"));
				board.setBoard_re_seq(rs.getInt("board_re_seq"));
				board.setBoard_readcount(rs.getInt("board_readcount"));
				board.setBoard_date(rs.getTimestamp("board_date"));
				
				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;
	}

- 여러개의 데이터를 구해야하므로 리턴자료형은 List 이다

- 먼저 리스트를 만들고 그 리스트에 검색한 데이터들을 저장한 다음 그 리스트를 반환

- DTO 객체 board 생성 후, 검색결과를 컬럼단위로 잘라서 setter 로 board 에 저장 후 그 board 를 리스트 list 에 저장 

<SQL문>

- 첫번째 서브쿼리문에서는 rownum 컬럼의 별칭을 rnum 으로 설정하고, 두번째 서브쿼리에서 검색한 모든 컬럼을 다 사용 (board.*)

- 두번째 서브쿼리문에서 원하는 방식으로 정렬을 한다. 두번째 서브쿼리의 별칭이 board

- board_re_ref 기준 내림차순으로 한번 정렬을 한다

- 부모와 그 부모의 댓글들은 모두 같은 ref 값을 가지고 있으므로 두번째 정렬을 board_re_seq 기준 오름차순으로 한다

- 전체 데이터를 원하는 방식으로 정렬한 곳에서 where 문으로 rnum >= startRow and rnum <= endRow 로 글들 가져오기

 

- getAttribute() 사용 후 다운캐스팅 하고 표현식 태그로 출력하는 대신 EL 로 쉽게 처리

 

- 이제 포워딩 할 페이지인 qna_board_list.jsp 에서 request 객체로 공유한 리스트, 변수들을 여기서 뿌려줘야함

- WebContent/board 폴더 안에 qna_board_list.jsp 생성 및 작성

- qna_board_list.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>
게시판 목록
</body>
</html>

 

- model 1 처럼 이 파일 qna_board_list.jsp 를 그냥 실행해선 안된다, 바로 실행 시 공유된 데이터가 없다

- 현재는 글 작성 후 목록 가져오기 요청을 통해 Controller 클래스로 가서 필요한 목록을 가져온 후 이 페이지로 와야함

- 매번 글 작성 후 목록으로 가기 힘드므로 개발 중간에 목록을 확인하면서 개발을 하기 위해 index.jsp 파일에 목록을 가져오기 요청을 하는 코드를 써서 쉽게 목록 페이지로 갈 수 있도록 하자

- 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>
모델2 게시판

<script>
	location.href = "./BoardListAction.do";
</script>
</body>
</html>

- index.jsp 에서 목록 가져오기 요청을 한다

- index.jsp 실행 시 목록을 가져오는 요청을 Controller 클래스에 하고 Service -> DAO -> Service 로 목록을 가져왔다가 qna_board_list.jsp 로 포워딩 됨

- index.jsp 실행 시

- 목록 구하는 요청 부터 qna_board_list.jsp 까지 온 것 이다, "게시판 목록" 출력은 qna_board_list.jsp 의 코드이다

- boardlist 가 찍힌 것을 봐서 Controller -> Service -> DAO -> 다시 Service 까지 왔다

 

- 다시 qna_board_list.jsp 로 돌아오자

- qna_board_list.jsp (수정 후 1, 목록 뿌리고 있다, 아직 블럭관련은 안함, 미완성)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %> 개 <br>

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<td>번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>날짜</td>
		<td>조회수</td>
	</tr>
	
	<c:set var="num" value="${listcount - (page - 1) * 10}"/>
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>
			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEE요일"/>
		</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>
</table>

 

공유 값 가져오기 (qna_board_list.jsp 부분)

- BoardListAction 에서 공유한 값들을 사용해서 출력만 시켜야한다, 테이블을 이용해서 출력

- 공유된 값이 기본자료형이라면 바로 ${네임값} 으로 출력 가능하다

- 공유된 값이 리스트일 때는 JSTL forEach 태그의 items 속성에 공유 네임이 들어가야한다

	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>${b.board_date}</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>

- 그 forEach 태그 안에서 리스트의 값(글)들을 변수 b 로 받아서 반복문으로 출력

- 그 글의 제목, 작성자 들을 출력하기 위해서 ${b.board_subject}, ${b.board_writer} 사용

- 필드들은 private 임, 직접 접근이 아니라 이 EL ${b.board_subject}, ${b.board_writer} 은 내부적으로 b.getBoard_subject , b.getBoard_writer() 과 같은 의미임

- 주의 : 표현식 태그 <%=number --%> 와 달리 EL 에서는 증감연산자 지원안되므로 ${num --} 불가능, 화면 출력시 ${num} 출력 후 set 태그로 재정의해서 1 을 감소시켜야함

 

화면 출력 번호 (qna_board_list.jsp 부분)

	<c:set var="num" value="${listcount - (page - 1) * 10}"/>

- 화면에 출력되는 번호는 마지막 파생변수로서 여기서 JSTL set 태그로 그 변수 num 을 만들어준다

+ 10 은 limit 값

 

원문 작성 폼으로 가는 링크 (qna_board_list.jsp 부분)

<a href="./BoardForm.do">글작성</a> <br>

- '글작성' 에 링크를 걸어서 원문 글 작성 폼으로 가도록 하자, 바로 갈 수 없고 먼저 Controller 클래스로 요청해야함

- Controller 클래스에 "/BoardForm.do" 로 요청시 원문 글 작성 폼으로 가는 코드를 작성해야한다

* Controller 클래스 코드는 아래에

* qna_board_list.jsp 모두 작성 후 실행 해서 확인하는 것은 아래에

 

원하는 포맷으로 날짜 출력 (qna_board_list.jsp 부분)

			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEEE"/>

- JSTL 국제화 라이브러리 사용

- EEEE 는 자동으로 "요일" 까지 뒤에 붙여줌

- EL 과 SimpleDateFormat 은 같이 사용 불가, 이제부터는 JSTL 국제화 라이브러리로 원하는 포맷으로 날짜 출력하기

 

댓글인 경우에는 제목 앞에 여백주기 (qna_board_list.jsp 부분)

		<td>
			<!-- 댓글 제목 앞에 여백 처리 -->
			<c:if test="${b.board_re_lev > 0}">
				<c:forEach var="i" begin="0" end="${b.board_re_lev}">
					&nbsp;
				</c:forEach>
			</c:if>
			${b.board_subject}
		</td>

- 댓글을 구별하기 위해 여백주기

- 공유로 넘어온 리스트에서 forEach 로 가져온 글 하나의 board_re_lev 가 0 보다 크면 댓글이라는 의미

- 댓글인 경우 forEach 루프를 돌리며 댓글 깊이(board_re_lev) 만큼 여백을 주기

 

제목 클릭시 링크로 상세페이지로 넘어가기 (qna_board_list.jsp 부분)

<td>
	<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>

- 상세페이지로 갈때의 요청은 "/BoardDetailAction.do" 로 하자

+ 상세페이지 Service 클래스명도 BoardDetailAction.java

- 요청하면서 현재 글 번호와 페이지 번호를 가져가야한다

- ?board_num=${b.board_num}&page=${page} 를 뒤에 붙여서 상세페이지로 넘어갈때 get 방식으로 상세페이지로 이 값들을 전달한다

 

- 공유 설정을 했던 BoardListAction.java 부분 (글 목록 가져오는 Service 클래스)

- 현재 페이지 번호 page 는 BoardListAction.java (글 목록 가져오기 Service 클래스) 에서 기본자료형으로 공유설정되었으므로 ${page} 로 가져올 수 있다

- 현재 글 번호는 BoardListAction.java 에서 기본자료형으로 공유설정되지 않았고, 공유설정된 리스트 boardlist 에서 가져와야하므로 ${b.board_name} 으로 가져왔다


EL 을 쓰지 않고 공유된 값을 출력하려면?

글갯수 : ${listcount } 개 <br>

- 이 코드를 EL 로 작성 (아래)

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %>

- getAttribute() 를 사용해야하고, 다운캐스팅 명시와 언박싱을 해야한다

- EL을 사용하는 것이 훨씬 간편하다


Controller 클래스에서 원문 글 작성폼으로 전달 (이동)

		// 글 작성폼
		} else if(command.equals("/BoardForm.do")) {
			try {
				forward = new ActionForward();
				forward.setRedirect(false);
				forward.setPath("./board/qna_board_write.jsp");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

- DB 연동을 하지 않으므로 Service 클래스로 가지 않고 바로 폼으로 감

- ActionForward 객체 foward 를 여기서 생성 후 바로 포워딩 방식, 포워딩 페이지(원문 글 작성폼) 설정

 

- index.jsp 를 실행해서 목록인 qna_board_list.jsp 로 이동 한 후 '글작성' 클릭해보자

- 글 작성폼으로 잘 넘어간다


- qna_board_list.jsp 작성이 끝난 후 확인을 위해 index.jsp 를 실행해서 목록을 가져온 뒤 View qna_board_list.jsp 로 오자

* 아직 블럭에서 페이지 1 2 3 ... 10 중 페이지를 선택하는 건 구현하지 않았음

- '글작성' 을 눌러서 원문 글 작성도 한번 더 해보자

- 최신순으로 정렬되어있다, 화면의 번호는 글 번호 board_num 이 아니다

- 현재 페이지의 가장 위 게시물의 번호를 구하고, 그 번호를 1씩 감소시켜서 나타낸 번호이다

+ Recent posts