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"> </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}">
</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씩 감소시켜서 나타낸 번호이다