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

앞으로 학습할 내용

- Model 2 회원관리 프로그램, 게시판 프로그램

- lib 폴더 안에 라이브러리 저장하는 대신 maven 으로 라이브러리 관리

- DB 접속 방법 중 MyBatis 사용해서 Model 1, Model 2 와의 연동

- (기존 DAO 클래스 안의 내용이 xml 로 처리됨)

- Spring 수업

 

+ JSP 와 오라클 연동 3가지 방법

 


 

+ session : 회원관리 부분에서만 사용, 로그인 ~ 로그아웃

 

프로젝트 구성도

- 회원가입폼, ID중복검사, 로그인 폼, 로그아웃 구현했다

- 이제 수정, 삭제(탈퇴) 기능을 해야함


회원관리 프로그램 : 정보수정

- 먼저 main.jsp 에서 '회원정보 수정' 에 링크를 걸어주자

- main.jsp (수정 전 4)

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

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="./UpdateMember.do">회원정보 수정</a> <br>
	<a href="./Logout.do">로그아웃</a> <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 요청이름값을 "/UpdateMember.do" 로 하자

- a href="./UpdateMember.do" 로 해주었다, 만약 안되면 앞에 프로젝트 명도 써야한다

+ 나중에 Service 클래스도 이름을 맞춰서 UpdateMember.java 로 만들기

- '회원정보 수정' 을 누르면 수정폼으로 이동한다, 수정폼에 사용자의 상세정보를 뿌려주기 위해 DB 연동 필요, Controller -> Service 로 가야한다

 

- Controller 에 연결하기 전에 Service 클래스인 UpdateMember.java 를 먼저 만들자

 

회원관리 프로그램 : 회원정보 수정폼 Service 클래스

- 회원 정보 수정폼을 처리하는 UpdateMember.java 파일을 만들자

- '회원정보 수정' 요청이름값과 동일하게 설정

- service 패키지에 UpdateMember.java 클래스 생성 및 작성

- UpdateMember.java

package service;

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

import dao.MemberDAO;
import model.MemberDTO;

public class UpdateMember implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("UpdateMember");
		
		HttpSession session = request.getSession();
		String id = (String)session.getAttribute("id");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO member = dao.getMember(id);	// 1명의 상세정보 구하기
		System.out.println("수정폼:" + member);
		
		String hobby = member.getHobby();	// hobby = "공부-게임-등산-"
		String[] h = hobby.split("-");		// h[0] = "공부", h[1] = "게임", h[2] ="등산"
		
		// 공유 설정
		request.setAttribute("member", member);
		request.setAttribute("h", h);
		
		// request 객체로 공유를 한 경우에는 dispatcher 방식으로 포워딩을 해야 view 페이지에서 공유한 값에 접근 가능
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);	// dispatcher 방식으로 포워딩
		forward.setPath("./member/updateform.jsp");
		return forward;
	}

}

- 진짜 수정 전에 id 로 1명의 상세정보를 구한 후 수정폼에 뿌려주는 역할을 해야한다

<회원 정보수정 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어온다

- ActionForward 객체 forward 생성 후 필드값을 설정해서 execute() 를 호출한 Controller 클래스에 돌려줌

- 진짜 수정 전에 id 로 1명의 상세정보를 구한 후 수정폼에 뿌려주는 역할을 해야한다

 

<회원 정보수정 Service 클래스 작성 순서>

1. id 를 통해 현재 로그인된 한명의 상세정보를 구해와야하므로 세션 값을 구해와야한다

- 먼저, session 객체를 생성하고 "id" 네임으로 세션에 공유된 id 를 가져온다

2. DAO 객체를 생성하고 회원 정보를 구해오는 하는 DAO 의 getMember() 메소드 호출

- getMember() 는 1명에 대한 정보를 검색하므로 리턴자료형은 DTO

3. getMember() 메소드로부터 리턴받은 member 객체와 취미를 request 객체로 공유설정

- 아래에 설명 : <DAO 에서 getMember() 로 회원 정보를 구한 뒤 다시 Service 클래스로 돌아왔을때>

4. 포워딩 방식과 포워딩 페이지 설정

- request 객체로 공유했으므로 포워딩 2가지 방법 중 dispatcher 방식으로 포워딩 해야 view 페이지에서 공유된 값을 가져올 수 있음

 

<DAO 에서 getMember() 로 회원 정보를 구한 뒤 다시 Service 클래스로 돌아왔을때>

* 아래의 DAO 를 작성 한 후 여기로 돌아왔다

- DAO로 부터 받아온 1명의 상세 정보를 저장하는 객체 member 전체를 공유설정해야한다

- 취미는 공유설정 전에, 객체 member 로 받은 값 중 취미를 가져와서 split() 로 잘라야한다, 그래야 수정폼에서 체크가 되어있게 할 수 있다

- 취미를 자른 후 각각의 취미를 배열 h에 저장하고 그 배열 h를 공유설정함

- 이후 포워딩 방식과 포워딩 페이지를 설정하고 객체 forward 를 리턴하며 다시 Controller 클래스로 돌아가서 updateform.jsp로 포워딩함

- request 객체로 공유했으므로 포워딩 2가지 방법 중 dispatcher 방식으로 포워딩 해야 view 페이지에서 공유된 값을 가져올 수 있음

 

* 아래에 DAO getMember() 메소드 작성

- 로그인 후 main.jsp 에서 '회원정보 수정' 클릭시

- Controller 클래스까지 잘 도착했고 command 에 알맞은 값이 들어갔음을 확인 가능

 

- Controller 클래스에서 요청을 Service 클래스로 연결시켜주자

 

Controller 클래스에서 회원정보 수정폼 Service 클래스로 가기

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

- MemberController.java 부분

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

		// 회원정보 수정폼
		} else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

 

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

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

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

- 나중에 execute() 가 실행된 후 Service 클래스에서 돌아올때는 Service 클래스에서 값이 설정된 forward 객체를 받아서 이 코드 아래의 포워딩 코드에서 포워딩 처리된다.

 

DAO 클래스 회원 1명 상세 정보 구하기 기능 메소드 작성

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

- MemberDAO.java 추가된 getMember() 부분 코드만

	// 회원 1명 상세 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery(); // SQL문 실행
			
			if(rs.next()) {
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} 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 member;
	}

- 수정폼 뿐만 아니라, 수정, 삭제시에도 사용될 메소드이다

- 1명에 대한 정보, 즉 데이터 1개이므로 리턴 자료형은 DTO, 매개변수는 사용자의 id

- select문이므로 ResultSet 객체 rs 도 만들었음

- 이제 DAO -> Service 로 리턴되고, 넘어온 값들을 Service 에서 request 객체로 공유해야한다, dispatcher 방식 포워딩해야함

 

- 이제 포워딩될 수정폼 View 페이지인 updateform.jsp 를 생성해야한다

- member 폴더 안에 수정폼 updateform.jsp 를 생성 및 작성

- updateform.jsp

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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 수정 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>


<script>
$(document).ready(function(){
	/* 취미 체크되도록 하기 */
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
	
	/* 라디오 버튼 체크  */
	$("input:radio[name='gender'][value='${member.gender}']").attr("checked", true);
});
</script>
<!-- 휴대폰 번호 첫번째 자리 선택-->
<script>
$(document).ready(function(){
	
	$("#phone1 option[value=${member.phone1 }]").attr("selected", true);
});
</script>
</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Update.do">
<input type="hidden" name="id" value="${member.id }">
<table border=1 width=500 align=center>
	<caption>회원 수정</caption>
	<tr><td>ID</td>
		<td>
		<%
			MemberDTO mem = (MemberDTO)request.getAttribute("member");
		%>
		<%=mem.getId() %> <br>
		${member.id}
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td>
			<input type=text id="name" name="name" value="${member.name }">
		</td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" 
		name="jumin1" value="${member.jumin1 }">-
			<input type=text size=7 maxlength=7 id="jumin2" 
		name="jumin2" value="${member.jumin2 }">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" 
		name="mailid" value="${member.mailid }">@
		    <input type=text size=10 id="domain" 
		name="domain" value="${member.domain }">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" 
		maxlength=4 value="${member.tel1 }">-
			<input type=text size=4 id="tel2" name="tel2" 
		maxlength=4 value="${member.tel2 }">-
			<input type=text size=4 id="tel3" name="tel3" 
		maxlength=4 value="${member.tel3 }">
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" 
			maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" 
			maxlength=4 value="${member.phone3 }">
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post" value="${member.post }">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td>
		<input type=text size=45 id="address" name="address" value="${member.address }">
		</td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" 
			placeholder="자기소개를 100자 이내로 입력하세요">${member.intro }
			</textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원수정">
			<input type=reset value="취소" onClick="history.go(-1)">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

0. memberform.jsp 파일 내용 복붙 후 수정

1. JSTL 코어 라이브러리를 불러오기

2. 수정폼에서 취미가 체크되도록 하는 처리를 한다 * 아래에 설명

3. 수정 시 요청하는 요청이름값을 /Update.do 로 하자, 폼태그의 action 값을 /Update.do 로 수정, 

4. id 값은 회원을 식별하는 기준이 되므로 id 값은 수정해선 안됨, id 입력양식 지우고 단순 출력하기 * 아래에 설명

5. 비밀번호를 제외한 입력양식 태그에 value 속성값들을 설정해서 뿌려준다

- 이름, 이메일 등 value 속성에서 EL 태그 사용해서 출력, textarea 는 태그 사이에 출력

- 휴대폰 첫번째 자리, 라디오박스 은 다른방식으로 처리한다 * 아래에 설명

6. '취소' 를 누르면 다시 main.jsp 로 돌아가도록 만들기

7. id 값은 Update.do 로 넘어가지 않기 때문에 hidden 으로 id 값을 전달


* 값 뿌리기 : 취미 체크 설정 (updateform.jsp 부분)

<!-- 취미 체크되도록 하기 -->
<script>
$(document).ready(function(){
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
})
</script>

- 공유 설정되어있는 취미를 저장한 배열 h 를 가져옴

- items 에 "${h}" 로 공유설정된 h 네임값의 값인 배열을 가져와서 forEach 태그로 각각의 원소(취미)를 s로 가져옴

- input type 이 checkbox 이고 name 값이 hobby이고, 그 취미 s를 value 로 가지고 있는 체크박스를 반복문에서 하나씩 선택해서 체크함

- 그럼 수정폼에서 체크표시가 되어 나타난다


* 값 뿌리기 : id 단순 출력하기 (updateform.jsp 부분)

- 공유설정되어있는 MemberDTO 객체 member 를 가져와야한다

- getAttribute() 를 할때 MemberDTO 로 다운캐스팅해야함

- EL 을 사용하지 않고 출력하는 방법, EL 로 출력하는 방법 둘 다 해보자 

 

- EL 을 사용하지 않고 출력하는 방법

		<%
			MemberDTO mem = (MemberDTO)request.getAttribute("member");
		%>
		<%=mem.getId() %>

- EL 을 사용해서 출력하는 방법

		${member.id}

- member 는 request 객체로 공유된 네임

- ${member} 는 member 라는 네임으로 공유된 객체 를 의미하고, ${member.id} 는 그 객체의 id 값을 출력하라는 의미

- .필드명을 쓴다고해서 id 필드에 직접 접근하는 것이 아님, 표기만 그럴뿐 실제로는 member.getId() 처럼 getter 메소드로 가져오는 것이다. 

 

- 다시 수정폼에 들어가보면 두 경우 다 id 를 잘 출력하고 있다

 

공유 시 주의

- setAttribute(A,B) 에서 A 는 네임이고 B는 값인데, B는 Object 형이다, 즉 모든 자료형이 다 올 수 있음

- getAttribute(A) 는 리턴 자료형이 Object 이다, 원래 저장되었던 자료형으로 다운캐스팅 해야한다


request 공유 값 크게 3가지 형태가 들어감, 값의 형태에 따라 공유값을 구해오는 방법

1. 기본자료형 변수 가 공유되었을때

- 그냥 공유네임 으로 구해오면 된다

ex) setAttribute("name","Lay") 였을때 "Lay" 를 가져오기

${name}

 

2. DTO 객체 가 공유되었을때

- 구해올때는 공유네임.필드명 으로 구해오면 된다

ex) 객체 member 에 대해 setAttribute("member",member) 를 했을때, 그 객체에서 id 값을 가져오고 싶다면

		${member.id}

ex) 객체 member 에 대해 setAttribute("member",member) 를 했을때, 그 객체에서 name 값을 가져오고 싶다면

			<input type=text id="name" name="name" value="${member.name }">

 

3. 배열, 리스트 가 공유되었을때

- 구해올 때는 공유네임이 forEach 태그의 items 값으로 들어가야한다

ex) String 배열  h 에 대해 setAttribute("h",h) 를 했을때, 그 배열의 원소들을 차례로 가져오고 싶다면

<!-- 취미 체크되도록 하기 -->
<script>
$(document).ready(function(){
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
})
</script>

+ forEach 태그는 더이상 원소값이 없을때까지 돌아간다


* 값 뿌리기 : 라디오버튼 체크 설정 (updateform.jsp 부분)

<!-- 라디오 버튼 체크 -->
<script>
$(document).ready(function(){
	$("input:radio[name='gender'][value='${member.gender}']").attr("checked", true);
	
})
</script>

- if 조건문을 사용해서 처리할 수도 있지만 jQuery 로 처리해보자

- gender 라는 컬럼에 "남자" 또는 "여자" 가 들어가 있다

- input type 이 radio 이고, name 값이 gender 이고, value 로 member 객체의 gender 필드값과 같은 값을 가지고 있는 라디오버튼 태그를 선택

 

- 그럼 수정폼에서 라디오버튼에 표시가 잘 나타난다


* 값 뿌리기 : 휴대폰 번호 첫번째 자리 (updateform.jsp 부분)

<!-- 휴대폰 번호 첫번째 자리 선ㄱ-->
<script>
$(document).ready(function(){
	
	$("#phone1 option[value=${member.phone1 }]").attr("selected", true);
});
</script>

- jQuery 로 처리하지 않으면 코드가 복잡해진다

- 가장 먼저 select 의 id 값으로 select 태그를 가져와야한다. #phone1 으로 id 값을 구하기

- 한칸 띄우면 자식 태그를 의미하므로 #phone option 으로 설정

- 그 option 태그들 중 value 가 ${"member.phone1"} 인 태그를 selected 한다

 

- updateform.jsp 작성 완료했다

- 이제 로그인 후 '회원정보 수정' 을 눌러서 수정폼으로 가자

 

- "/Update.do" 가 요청이름값이다

 

회원관리 프로그램 : 회원 정보수정 Service 클래스

- 실제 수정을 수행하는 Update.java 클래스를 service 패키지 안에 생성하자

- Update.java 파일에서 비번이 틀리면 다시 수정폼으로 돌아가도록 하고 비번이 맞으면 수정하자

- Update.java

package service;

import java.io.PrintWriter;

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

import dao.MemberDAO;
import model.MemberDTO;

public class Update implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Update");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String[] hobby = request.getParameterValues("hobby");
		String h = "";
		for(String h1 : hobby) {
			h += h1 + "-";	// h = "공부-게임-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(member.getId()); // 1명의 상세정보 구하기
		
		// 비번 비교
		if(old.getPasswd().equals(member.getPasswd())) { // 비번 일치시
			int result = dao.update(member);	// update SQL문 실행
			if(result == 1) System.out.println("회원 수정 성공");
		} else { // 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}

		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("./member/main.jsp");
		return forward;
	}

}

- 수정 폼에서 넘어온 값들을 처리하기 위한 Service 클래스

 

<회원 정보수정 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어온다

- ActionForward 객체 forward 생성 후 필드값을 설정해서 execute() 를 호출한 Controller 클래스에 돌려줌

- 수정 전에 id 로 DB에서 사용자의 비밀번호를 가져온 후 일치되어야 수정한다

 

<회원 정보수정 Service 클래스 작성 순서>

1. 한글값 인코딩, 한글 문서 인코딩

- 수정폼에서 한글값이 넘어오므로 한글값 인코딩 request.setCharacterEncoding()

- 한글 문서(값) 깨지지 않도록 인코딩 response.setContentType()

2. memberInsert.java 의 일부 내용을 가져와서 붙여넣기

3. DB에 저장된 비밀번호와 사용자가 입력한 비밀번호(member 객체 안에 저장되어있음)를 비교한다

- 수정폼에서 사용했던 1명의 상세정보를 구하는 getMember() 메소드를 사용해서 1명의 상세정보를 구해옴

- 그 상세정보에서 비밀번호만 가져오면 된다 old.passwd

4. 사용자가 입력한 비밀번호가 DB의 비밀번호와 일치시 수정을 하는 DAO 메소드 Update() 를 호출

* DAO 클래스는 아래에 작성

5. 비밀번호가 불일치시 out 객체를 사용해서 자바스크립트로 메세지 출력

- 먼저 out 객체를 생성, 출력하기, out 객체 닫기

- return null 로 해야 아래를 실행하지 않기때문에 history.go(-1) 에 의해 다시 수정폼으로 돌아갈때 에러나지 않음

+ out 객체 안에 자바스크립트 코드 작성 가능, 바깥에 " " 면 안은 ' '

6. forward 객체에 포워딩 방식, 포워딩 페이지 설정

- 아직 session 이 끊어지지 않았으므로 redirect, dispatcher 방식 포워딩 둘 다 사용 가능

- 수정이 끝나면 main.jsp 로 돌아가도록 한다

 

- 이후 Controller 클래스에서 수정폼에서 들어온 "/Update.do" 요청을 Update.java Service 클래스로 넘겨주는 처리를 하자

- 즉 Controller 클래스에서 연결시켜주자

 

Controller 클래스에서 회원정보 수정 Service 클래스로 가기

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

- MemberController.java 부분

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

		// 회원정보 수정
		} else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			} catch (Exception e){
				e.printStackTrace();
			}
		}

- 이러면 "/Update.do" 요청을 Controller 클래스에서 Update.java Service 클래스로 보내줌

 

DAO 클래스 회원정보 수정 기능 메소드 작성

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

- MemberDAO.java 추가된 Update() 부분 코드만

	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "update member2 set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql += "domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql += "post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());

			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;
	}

 

- 다 완성되었다

- 로그인폼에서 로그인 한 후 main.jsp 에서 '회원정보 수정' 으로 수정폼으로 가서 수정해보자

- 맞는 비밀번호 입력시

- 회원정보가 잘 수정된 후 main.jsp 로 왔다

- 다시 수정폼에 들어가보면 정보가 수정되었음을 확인 가능

- 콘솔창


회원관리 프로그램 : 회원 탈퇴

- 먼저 main.jsp 에서 '회원탈퇴' 에 링크를 걸어주자

- main.jsp (수정 후, 최종, 수정 전 4 다음 코드)

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

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="./UpdateMember.do">회원정보 수정</a> <br>
	<a href="./Logout.do">로그아웃</a> <br>
	<a href="./DeleteMember.do">회원탈퇴</a> <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 회원탈퇴 클릭시 콘솔창

- "/DeleteMember.do" 로 요청해서 회원 탈퇴폼으로 갈 것

- 회원탈퇴 폼으로 갔을때는 DB연동이 필요하지 않으므로 Service 클래스로 갈 필요 없다

 

- Controller 클래스에서 "/DeleteMember.do" 요청을 처리하는 코드를 작성하자

 

Controller 클래스에서 회원 탈퇴폼 Service 클래스로 가기-

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

- MemberController.java 부분

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

		// 회원탈퇴 폼
		} else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/deleteform.jsp");
		}

- DB연동하지 않으므로 Service 클래스로 가지 않아도 된다

- ActionForward 객체 foward 를 생성하고 forward 객체 값 설정

- 그리고 deleteform.jsp 로 포워딩

 

- 이제 member 폴더 하위에 삭제폼 deleteform.jsp 를 생성 및 작성

- deleteform.jsp

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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 삭제 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Delete.do">
<input type="hidden" name="id" value="${sessionScope.id }">
<table border=1 width=500 align=center>
	<caption>회원 삭제</caption>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원삭제">
			<input type=reset value="취소" onClick="history.go(-1)">
		</td>
	</tr>		
</table>
</form>

</body>
</html>

- updateform.jsp 내용을 복붙 후 많은 내용 삭제 및 수정

- 비번만 맞으면 탈퇴시키기 위해 삭제 폼에서 비번을 입력받아야함

- 아이디와 비밀번호만 "/Delete.do" 로 넘기면 된다

- 아이디를 넘길때는 sessionScope.id 로 공유된 세션에서 id 를 가져와서 hidden 으로 /Delete.do 로 넘김

- form 태그 action 을 "Delete.do" 로 바꿈, 즉 삭제를 할때 "/Delete.do" 로 요청할 것

- 이제 삭제폼에서 비번 입력 후 '회원삭제' 클릭 시 "/Delete.do" 로 요청한다

- 삭제폼 까지 완료했다, main.jsp 에서 '회원 탈퇴' 클릭시 콘솔창

 

회원관리 프로그램 : 회원 탈퇴 Service 클래스

- 실제 삭제 수행하는 Delete.java 클래스를 service 패키지 안에 생성하자

- Delete.java 파일에서 비번이 틀리면 다시 수정폼으로 돌아가도록 하고 비번이 맞으면 수정하자

- Delete.java

package service;

import java.io.PrintWriter;

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

import dao.MemberDAO;
import model.MemberDTO;

public class Delete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Delete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
	
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(id);	// 1명의 상세 정보 구하기
		
		// 비번 비교
		if(old.getPasswd().equals(passwd)) {	// 비번 일치시
			int result = dao.delete(id);
			if(result == 1) System.out.println("회원탈퇴 성공");
			
			session.invalidate(); // 세션 삭제
		} else {	// 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("./member/loginform.jsp");
		return forward;
	}

}

1. 현재 문서의 한글값 인코딩, 한글값이 넘어올 수 있으므로 한글값 인코딩

2. out 객체(출력스트림 객체) 생성, 회원 삭제를 하면 세션을 끊어야하므로 session 객체 생성

3. hidden 으로 넘어온 id 값과, 비번 입력양식에 넘어온 비번값을 받아서 변수에 저장

4. DB에서 비번을 가져와서 사용자가 삭제폼에 입력한 비번과 일치하는지 확인

- 수정폼에서 사용했던 1명의 상세정보를 구하는 메소드인 getMember() 로 상세 정보를 구해서 거기서 비번 가져오기

5. 비번이 일치하면 DAO 의 delete() 메소드를 호출해서 회원 삭제, 세션을 invalidate() 메소드로 강제 삭제

6. 비번 불일치시 out 객체를 사용해서 자바스크립트로 메세지 박스를 뿌리고, 이전 페이지인 main.jsp 로 이동

- return null 로 빠져나가서 아래의 forward 관련 코드 실행 못하도록 하기

7. ActionForward 객체 forward 로 포워딩 방식, 포워딩 페이지 설정

- return null 때문에 이 코드는 비번이 틀리면 수행하지 않음, 비번이 맞고, 회원탈퇴에 성공한 경우에만 실행됨

- 회원 탈퇴 성공 후 포워딩 될 페이지는 loginform.jsp

- 이후 이 Service 클래스의 execute() 를 부른 Controller 클래스로 다시 돌아감

 

- "/Delete.do" 로 요청이 왔을때 이 Service 페이지로 오도록  Controller 클래스에 연결해주는 코드를 작성하자

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

 

Controller 클래스에서 회원 탈퇴 Service 클래스로 가기

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

- MemberController.java 부분

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

		// 회원 탈퇴
		} else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 회원 탈퇴 (삭제) 기능 메소드 작성

	// 회원 탈퇴
	public int delete(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "delete from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			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;
	}

- id 를 받아서 그 한명의 정보를 삭제 하면 된다

- 이걸 실행 후 DAO -> Service 클래스 -> Controller 클래스 로 돌아간다

 

회원관리 프로그램 : 초기 페이지

- 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 = "./LoginForm.do"
</script>
</body>
</html>

- 실행 시 바로 로그인 폼으로 넘어가도록 한다

- "./LoginForm.do" 로 요청 시 Controller 클래스에서 로그인 폼으로 넘어감

 

- 삭제 기능이 잘 구현되었는지 확인해보자

- 현재 프로젝트 model2member 를 선택하고 오른쪽 마우스 : Run As -> Run on Server 선택시 실행된다

1. 자동으로 index.html 로 가서 로그인 페이지를 띄움

2. 로그인 후 main.jsp 로 가기

3. '회원탈퇴' 클릭 후 삭제 폼에 맞는 비번 입력

 

4. 삭제시 로그인 폼으로 다시 넘어간다

- DB 에서 삭제 확인

- 삭제 되었다, 삭제 기능 잘 구현되었음

 

문제점

- 가입 / 로그인 / 수정 / 삭제 성공 시 '수정 성공' , '삭제 성공' 와 같은 메세지가 뜨지 않는다

- out 객체를 MemberInsert.java 에 생성해서 out.println("alert();") 로 alert 창을 띄울 수 없다

 

문제 해결

- MemberInsert.java 에서 회원가입 성공 후 '회원가입' 성공 alert 메세지를 띄워보자

- out 객체를 MemberInsert.java 에 생성해서 out.println("alert()") 로 alert 창을 띄울 수 없으므로

- 따로 출력을 위한 jsp 파일을 만들어서 location 객체를 사용해서 이동해야한다

- 그리고 아래 이 코드 주석 처리한다, 성공 후 Controller 에서 포워딩 되지 않고 위처럼 location.href 로 result.jsp 로 가므로 아래에 return null; 을 해준다

- 이제 member 폴더 하위에 result.jsp 를 만들자

 

- result.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>

<script>
alert("회원가입 성공");
location.href = "<%=request.getContextPath()%>/LoginForm.do"
</script>

 

- 이제 회원가입을 시키면 성공 메세지가 뜨고 로그인 폼으로 넘어간다

 

- 이제 '회원가입 성공' 메시지가 제대로  뜬다

 


Controller 클래스 완성본

- MemberController.java (최종, 전체 코드)

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;
import service.Delete;
import service.IdCheck;
import service.Login;
import service.MemberInsert;
import service.Update;
import service.UpdateMember;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController 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); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		// ID중복 검사 (ajax)
		} else if (command.equals("/Idcheck.do")) {
			try {
				action = new IdCheck();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		// 회원가입 폼
		} else if (command.equals("/MemberForm.do")){
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/memberform.jsp");

		// 로그인 (회원 인증)
		} else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		// 로그인 폼	
		} else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/loginform.jsp");
			
		// 로그아웃
		} else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/logout.jsp");
			
		// 회원정보 수정폼
		} else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		// 회원정보 수정
		} else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			} catch (Exception e){
				e.printStackTrace();
			}
		// 회원탈퇴 폼
		} else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/deleteform.jsp");
		// 회원 탈퇴
		} else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		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() 호출
	}

}

DAO 클래스 완성본

- MemberDAO.java (최종, 전체 코드)

// DAO (Date Access Object)
package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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

import model.MemberDTO;

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행 하는 것.
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO 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();
	}
	
	// 회원가입
	public int insert(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = getConnection();
			
			String sql = "insert into member2 ";
			sql += "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());

			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;
	}
	
	// ID중복 검사 (ajax)
	public int idcheck(String id) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				result = 1;	// 중복 ID
			} else {
				result = -1; // 사용 가능한 ID
			}
			
		} 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;
	}
	
	// 로그인 (회원인증)
	public int memberAuth(String id, String passwd) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ? and passwd=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, passwd);
			rs = pstmt.executeQuery();	//SQL문 실행
			
			if(rs.next()) {	// 회원인증 성공
				result = 1;
			} else {		// 회원인증 실패
				result = -1;
			}
			
		} 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;
	}
	
	// 회원 1명 상세 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery(); // SQL문 실행
			
			if(rs.next()) {
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} 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 member;
	}
	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "update member2 set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql += "domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql += "post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());

			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;
	}
	// 회원 탈퇴
	public int delete(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "delete from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			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;
	}
}

 

복습

전체 흐름

 

Service 클래스

- 공통적인 내용들을 Action 이라는 인터페이스에 작성, 추상메소드 execute() 가 있다

- 각각의 클래스들은 인터페이스 Action 을 상속함, 반드시 추상메소드 execute() 를 오버라이딩 해야함

- 클라이언트의 요청을 실제로 처리

- DAO 와 함께 실제 DB 쪽에 데이터를 처리해주는 Model 이라고 불린다

 

- DB에서 검색한 결과를 DAO 에서 Service 클래스로 리턴함

- Service 클래스에서 돌려받은 객체를 공유설정함

- View 페이지에서 공유설정된 값을 EL + JSTL 으로 출력한다

 

Controller 클래스

- HttpServlet 클래스를 상속받는다, doPost(), doGet() 메소드를 오버라이딩 해야함

- WebServlet 어노테이션 이름값이 하나면 한곳에서 온 요청만 받을 수 있으므로 Controller 클래스를 여러개 만들어야한다

- WebServlet 어노테이션 이름값을 패턴을 지정해서 패턴에 맞으면 다 여기로 오도록 함

- 그 후 폼의 action 값들을 패턴에 맞게 지정한다, 그럼 Contorller 클래스로 찾아갈 수 있음

- 다른 웹사이트의 URL 주소를 보면 확장자가 보인다 ex) .do, .naver


회원관리 프로그램 : Controller 클래스 (이어서)

- MemberController.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 MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController 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); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

	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() 호출
	}

}

- Controller 클래스에서 매개변수로 받은 request, response 객체를 Service 클래스로 넘어갈때도 전달한다

- doPost(), doGet() 에서 받은 request, response -> doProcess() 호출시 넘겨줌 -> Service 클래스로 갈때도 넘겨줌

- doProcess() 에도 넘겨줘야 거기서 request.getRequestURI() 와 request getContextPath() 메소드를 사용가능

 

doProcess() 안의 내용 (MemberController.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); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

- command 에 저장되는 값에 구체적으로 어떤 요청이름기값으로 Controller 클래스로 왔는지 확인 가능

- 즉, command 에 저장된 값으로 어디서 요청이 왔는지 확인 가능하다

- command 값을 보고 어떤 서비스 클래스로 보낼지 결정한다

+ substring(13) 은 13 인덱스부터 끝까지를 자른다

 

- 결과 확인 1

- loginform.jsp form 태그

	<form method="post" action="<%=request.getContextPath() %>/Login.do">

 

- loginform.jsp 파일을 실행해서 '로그인' 을 눌러보자

+ 해당 Service 클래스 이름도 요청 이름값과 똑같이 작성할 것, Login.java

 

- 결과 확인 2

- memberform.jsp form 태그

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do">

- memberform.jsp 파일을 실행해서 '회원가입' 을 눌러보자

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

+ 해당 Service 클래스 이름도 요청 이름값과 똑같이 작성할 것, MemberInsert.java

 

실행 순서

1. loginform.jsp 또는 memberform.jsp 에서 action 태그로 지정된 곳을 따라 Controller 클래스 도착

2. post 방식으로 요청했으므로 오버라이딩 된 doPost() 실행됨, doProcess() 가 다음으로 실행됨

3. 3개의 변수값이 구해짐

- 최종적으로는 요청 이름값이 저장된 command 변수의 값이 필요하다

- 어떤 서비스 클래스로 보낼지 command 값을 보고 결정

 

회원관리 프로그램 : Service 클래스 1

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

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

- Service 에서 DAO 로 넘어가야한다

 

+ ActionForward 클래스

- 이 클래스에서 어떤 방식으로 포워딩 할지, 어디로 포워딩 할지 결정해줌

- redirect 변수는 포워딩 방식을 설정, path 는 포워딩할 페이지명 설정

- request 객체로 공유가 되면 포워딩 하는 방법 2가지가 있다

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

 

부모 인터페이스 만들기

- 이제 service 패키지를 만들고 인터페이스 Action 을 만들자

- 현재까지 Action 인터페이스

- 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 클래스 만들기

- 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;
	}
	
}

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

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

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

- 두 변수는 private 으로 설정

3. getter, setter 메소드 만들기

+ 이클립스가 redirect 변수의 getter 메소드를 자동으로 만들때 getRedirect() 가 아니라 isRedirect() 로 만들었음을 유심히 보기

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

 

- 다시 Controller 클래스로 돌아와서 Service 클래스로 넘겨주는 작업을 하자

- MemberController.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;
import service.MemberInsert;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController 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); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		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 로 초기화

- 다음은 요청에 따라 적절한 Service 클래스로 넘기기

- 마지막 부분에서 포워딩 처리를 한다, DAO -> Service -> Controller 로 돌아올때 포워딩 처리함

 

Service 클래스로 전달 (MemberController.java 부분)

		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		}

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

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

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

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

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

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

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

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

* MemberInsert 클래스는 아래에서 구현한 회원가입 처리 Service 클래스이다.

 

포워딩 처리 (MemberController.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 클래스는 두고, 회원가입 기능을 먼저 구현하자

- 먼저 회원가입 폼 memberform.jsp 를 보자

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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do"> 
<table border=1 width=500 align=center>
	<caption>회원 가입</caption>
	<tr><td>ID</td>
		<td><input type=text autofocus="autofocus" id="id" name="id">
			<input type=button value="ID중복검사" id="idcheck">
			<div id="myid"></div>
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid">@
		    <input type=text size=10 id="domain" name="domain">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4>-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4>-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4>
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4>-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4>
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address"></td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요"></textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원가입">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

 

유효성 검사를 하는 자바스크립트 불러오기 경로 (memberform.jsp 부분)

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

- Controller 클래스에 의해 왔다갔다 하면 상대경로이기 때문에 이 경로를 잘 못찾아갈 수도 있다

- 이땐 위의 코드처럼 프로젝트명을 적고 나머지는 WebContent 폴더 기준으로 하위폴더를 모두 쓰고 파일명을 적기

+ request.getContextPath() 메소드는 프로젝트 명을 구해줌 ex) /model2member

 

form 의 action 태그 (memberform.jsp 부분)

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do">

- post 방식으로 입력양식의 값들을 전송한다

- model 2 에서는 파일이 아닌 무조건 Controller 클래스로 찾아가야한다, 프로젝트명을 앞에 붙이면 Controller 클래스를 잘 찾아감

- 배포가 되면 프로젝트가 URL 경로상에서 빠지므로 프로젝트명을 직접 적는 것이 아닌 request.getContextPath() 사용

 

회원관리 프로그램 : 회원가입 Service 클래스

- 실제 회원가입 요청을 처리하는 MemberInsert.java 파일을 만들자

- 회원가입 폼의 요청이름값과 동일하게 설정

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

package service;

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

import dao.MemberDAO;
import model.MemberDTO;

public class MemberInsert implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("MeberInsert");
		
		request.setCharacterEncoding("utf-8");
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String[] hobby = request.getParameterValues("hobby");
		String h = "";
		for(String h1 : hobby) {
			h += h1 + "-";	// h = "공부-게임-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.insert(member);	// 회원가입
		
		if(result == 1) System.out.println("회원가입 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/loginform.jsp");	// 포워딩할 파일명
		
		return forward;
	}
}

<회원가입 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어왔다

- 매개변수를 통해 Controller 클래스의 request 객체가 그대로 넘어왔으므로, 폼에서 Controller 클래스로 넘겨준 form 의 각종 입력양식의 값들을 여기 Service 클래스에서 request.getParamter() 로 가져올 수 있다

- 필드값을 설정한 ActionForward 객체를 execute() 를 호출한 Controller 클래스에 돌려줌

 

<회원가입 Service 클래스 작성 순서>

1. 먼저 post 방식으로 한글값이 넘어올 수 있으므로 한글값 인코딩을 설정한다

2. DTO 객체를 만들고 setter 메소드로 값을 세팅

- jsp 에서 useBean, setProperty 로 DTO 객체 생성하고 값을 세팅했던 코드에 해당함

- 이때 DTO 개체에 설정하는 값은 폼-> 컨트롤러-> 서비스 클래스로 넘어온 request 객체를 사용해서 request.getParamter() 로 폼 입력양식의 입력값을 가져와서 설정

- 취미는 2개이상 선택해야하는 양식이므로 값을 getParamterValues() 로 받고 여러개의 값을 하이픈으로 결합해서 저장

+ Spring 에선 일일히 값을 전달하지 않아도 어노테이션으로 한번에 처리 가능

3. DAO 객체를 생성하고 회원가입 insert 를 하는 DAO 의 insert() 메소드 호출

- 매개변수로 값을 설정한 DTO 객체를 넘긴다

- insert() 가 잘 수행되었으면 1 을 리턴한다

4. insert() 가 끝난다음엔 Service -> 다시 execute() 를 호출한 Controller 로 돌아가야한다

- 이때 Controller 로 ActionForward 객체를 리턴해야한다

- 리턴하기 전에 어떤 방식으로 포워딩할지, 어느 파일로 포워딩할지를 setRedirect(), setPath() 로 설정

- 지금은 request 객체로 공유를 설정하지 않았으므로 dispatcher, redirect 두개의 포워딩 방식 모두 사용 가능, 현재는 dispatcher 방식으로 포워딩하자

- setPath() 메소드 안에는 WebContent 폴더를 기준으로 하위 폴더를 쓰면서 포워딩할 파일명을 적어주기

- 회원가입이 끝나면 loginform 으로 갈 것이므로 loginform.jsp 로 포워딩하자

 

- 그 후 객체 forward 를 리턴하면서 Controller 클래스의 이곳으로 간다

- MemberController.java 부분

- 요청을 받을때는 forward == null 이므로 만족하지 않았던 포워딩 처리의 조건문

- 이번엔 객체를 돌려받으므로 forward !=null 이 되어 포워딩 처리함

5. 이후 DAO 클래스를 작성 해야함

 

콘솔창 확인

- memberform.jsp 에서 '회원가입' 클릭시

- 여태까지 회원가입 폼 -> Controller 클래스  -> MemberInsert Service 클래스까지 넘어왔음을 알 수 있음

 

- 다음으로 DAO 클래스에서 insert() 메소드를 작성하자

- DAO 클래스의 코드는 Model 1 와 거의 같다

 

DAO 클래스 회원가입 기능 메소드 작성

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

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

	// 회원가입
	public int insert(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = getConnection();
			
			String sql = "insert into member2 ";
			sql += "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());

			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;
	}

- Model 1 에서 DAO의 insert() 메소드를 작성했던 것과 같은 방식으로 작성하면 된다

- Model1 DAO 회원가입 insert() 메소드 설명 : https://laker99.tistory.com/114?category=1080281

 

다음 흐름

- DB 에 insert 수행 후 DAO -> Service 클래스(MemberInsert.java) 로 간다

- Service 클래스에서 forward 객체를 생성 후 어디로 포워딩 할지, 어떻게 포워딩 할지 값만 설정한 후 forward 객체 리턴

- Service 클래스의 execute() 메소드를 호출한 Controller 클래스로 돌아가서 실제 포워딩을 수행한다

- Controller 클래스의 이곳으로 다시 돌아간다

- Controller 클래의 객체 forward 가 execute() 에서 리턴으로 ActionForward 객체를 받았으므로 더이상 fowrad 는 null 이 아님

- 처음에는 null 이었기에 실행되지 않았던 포워딩 코드를 이제는 null 이 아니므로 실행함

- getPath() 로 포워딩할 페이지를 불러와서 dispatcher 방식 또는 redirect 방식으로 포워딩 하면 된다

- 여기서는 dispatcher 방식으로 포워딩 했다

 

실행 캡처

- 먼저 memberform.jsp 회원가입 폼에서 회원가입 시키자

- 회원가입을 하면 로그인 페이지로 포워딩 된다, dispacher 방식 포워딩이므로 URL 변화 없음

- sql 파일에서 DB insert 되었는지 확인

- 삽입되었음을 확인 가능

 

- 콘솔창 보기

- Controller 클래스("post" 출력) -> Service 클래스("MemberInsert" 출력) -> DAO 클래스에서 DB에 삽입 -> Service 클래스("회원가입 성공" 출력) -> Controller 클래스 -> loginform.jsp 포워딩 을 콘솔창에서 찍혀진 결과로 확인 가능

 

회원관리 프로그램 : ID 중복검사

- memberform.jsp 파일을 보면

- 비동기적으로 입력창 아래에 사용 가능한 ID 메세지 띄우기

- Ajax는 만들어져 있으므로 저렇게 글자는 뜬다

- 해당 서비스 클래스를 만들어야 실제로 ID중복검사를 하고, 제대로 동작한다

 

- 이제 member.js 파일에서 Ajax 로 비동기적으로 서버창에 요청하는 코드를 보자

- Idcheck.do 로 요청한다, Controller 클래스로 찾아가서 doPost() 가 실행됨, 이때 URL 에는 /Idcheck.do 가 찍힌다

- 'ID중복검사' 버튼 클릭시 요청 이름값인 Idcheck.do 가 콘솔창에 나온다

- 요청을 했고 그 요청이 Controller 클래스까지 갔다는 의미이다

 

- 이제 요청을 실제로 처리하는 Service 클래스를 만들어야한다

 

회원관리 프로그램 : ID중복검사 Service 클래스

- 실제 ID중복검사 요청을 처리하는 Idcheck.java 파일을 만들자

- 회원가입 폼의 요청이름값과 동일하게 설정

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

- IdCheck.java

package service;

import java.io.PrintWriter;

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

import dao.MemberDAO;

public class IdCheck implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("IdCheck");
		
		// 출력 스트림 객체 생성
		PrintWriter out = response.getWriter();
		
		String id = request.getParameter("id");
		System.out.println("id:" + id);
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.idcheck();	// ID중복검사
		System.out.println("result:" + result); // 1 : 중복 ID //-1 : 사용 가능한 ID
		
		// 웹브라우저에 출력되는 값이 callback 함수로 리턴됨
		out.println(result);
		
		return null;
	}

}

* 아래의 Controller 클래스에서 이 Service 클래스로 가는 코드를 먼저 작성 한 후 다시 이 코드로 돌아와 작성했다

- 먼저 Action 인터페이스를 상속하고 execute() 메소드 오버라이딩

- 지금은 비동기로 처리하므로 Controller 클래스로 돌아가지 않는다! 그래서 return null을 함

- 돌아갈때 member.js 로 돌아가야한다, 이때 브라우저에 출력되는 값이 callback 함수로 리턴되므로 1 또는 -1 이 돌아감

- member.js 에서 요청되면서 넘어온 값을 request.getParamter("id") 로 받는다

- DAO 객체 생성해서 메소드 idcheck() 호출하며 사용자의 id 를 넘기기, 중복이면 1 리턴, 사용가능한 아이디면 -1 리턴

- 브라우저에 출력되는 값이 콜백함수로 리턴되므로, 이 IdCheck.java 에서 출력해야함, 출력위해 out 객체 생성

- out 객체로 DAO 의 idcheck() 에서 돌려받은 값 result 를 출력, 그럼 member.js 에서 data 로 돌려받음

+ Model 2 에선 JSP 의 내장객체 없다, request, response 만 받아서 사용 가능하고 나머지 객체는 직접 만들어야함

 

- 다음은 다시 Controller 클래스로 돌아가서 ID중복검사에서 요청이 올경우 IdCheck.java Service 클래스로 보내주는 코드 작성

 

Controller 클래스에서 ID 중복검사 Service 클래스로 가기

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

- MemberController.java 부분

		// ID중복 검사 (ajax)
		} else if (command.equals("/Idcheck.do")) {
			try {
				action = new IdCheck();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		}

- IdCheck() 클래스로 객체 생성하고, 그 객체에서 execute() 실행하기

- 이 코드 작성 후 회원가입 폼에서 'ID중복검사' 버튼 클릭시 콘솔창 확인하면

- IdCheck 가 찍히는 걸 보아, 요청하는 곳 -> Controller -> Service 클래스까지 잘 도착했음을 확인 가능

- id: lay99 이 찍히는 걸 보아, Ajax로 요청하면서 넘어온 값이 Service 클래스에서 잘 받았음을 확인 가능

 

- 이제 DAO 클래스에 ID중복검사를 하기 위한 메소드 idcheck() 작성하기

 

DAO 클래스 ID중복검사 메소드 작성

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

- MemberDAO.java 추가된 idchcek() 부분 코드만

- MemberDAO.java (추가)

	// ID중복 검사 (ajax)
	public int idcheck(String id) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				result = 1;	// 중복 ID
			} else {
				result = -1; // 사용 가능한 ID
			}
			
		} 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 객체도 생성

- select SQL문 실행 후 rs.next 가 true 인 경우엔 DB 에 중복아이디가 있다는 의미이다

- 중복 ID 면 1 을 리턴, 사용 가능한 ID 면 -1 을 Service 클래스 IdCheck.java 로 돌아가며 리턴

 

- memberform.jsp 를 실행해서 ID중복검사 를 해보자

- 이미 가입된 ID 입력시

- 콘솔창 확인

- Service 클래스에서 DAO 로 부터 받은 값을 Service 클래스에서 result 로 받아 출력하고 있다, 중복 ID 이므로 1 이 출력

 

- 가입되지 않은 ID 입력시

- 콘솔창 확인

- Service 클래스에서 DAO 로 부터 받은 값을 Service 클래스에서 result 로 받아 출력하고 있다, 사용 가능한 ID 이므로 -1 이 출력


회원관리 프로그램 : 로그인 기능

- 이제 로그인 기능을 구현하자

- 먼저 로그인 폼인 loginform.jsp 파일을 보자

- loginform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>로그인 폼</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	
	<!-- 외부 자바스크립트 파일 불러오기 -->
	<script src="<%=request.getContextPath() %>/member/login.js"></script>
	
	</head>
	<body>
	
	<form method="post" action="<%=request.getContextPath() %>/Login.do">
	<table border=1 width=350 align=center>
		<caption>로그인</caption>
		<tr>
			<td>ID</td>	
			<td><input type=text size=30 id="id" name="id" autofocus="autofocus"></td>	
		</tr>
		<tr>
			<td>비밀번호</td>
			<td><input type="password" size=30 id="passwd" name="passwd"></td>
		</tr>
		<tr>
			<td colspan=2 align=center>
				<input type="button" value="회원가입" 
				       onClick="location.href='<%=request.getContextPath()%>/MemberForm.do' ">
				<input type="submit" value="로그인">
				<input type="reset" value="취소">
			</td>
		</tr>
		
	</table>
	</form>
	
	
	</body>
	</html>

- 외부 자바스크립트 파일을 불러올때 request.getContext() 로 프로젝트명을 구하고. WebContent 기준 하위 폴더와 파일명 적기

+ '로그인' 버튼만 submit 버튼으로서 클릭시 입력된 값을 post 방식으로 전송, Controller 클래스로 간다

 

'회원가입' 으로 넘어갈때 주의

- '회원가입' 을 클릭시 바로 memberform.jsp 로 가는 것이 아님

- Model 2 이기 때문에 Controller 클래스를 갔다가 다시 가입 폼 (View)으로 가야함

- '회원가입' 클릭시 Controller 클래스로 먼저 가기 위해 memberform.jsp 대신 MemberForm.do 로 do 확장자로 요청해서 어노테이션으로 Controller 클래스로 찾아가게함

- Controller 클래스의 doGet() 메소드가 실행됨

- 로그인폼에서 '회원가입' 클릭시 콘솔창

- Controller 클래스로 간것을 확인 가능, 요청 이름값은 MemberForm.do

 

- Controller 클래스에서 MemberForm.do 로 요청이 올때 어떻게 처리할지를 작성해야한다

 

Controller 클래스에서 회원가입 폼으로 가기

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

- MemberController.java 부분

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

		// 회원가입 폼
		} else if (command.equals("/MemberForm.do")){
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/memberform.jsp");
		}

- DB 연동을 하는 경우가 아니므로 Service 클래스로 가지 않고 바로 회원가입 폼으로 가면 된다

- 바로 forward 객체를 생성하고 setRedirect(), setPath() 로 값을 설정해줌, 원래 이 작업은 Service 클래스 하단에서 해주는 거지만 여기선 Service 클래스로 가지 않으므로 여기서 값 설정

- 여기서 forward 객체는 null 이 아니게 되므로 아래의 코드인 포워딩 코드에서 포워딩되어 회원가입폼으로 간다

+ 모두 Service 클래스로 가는 것이 아님, DB연동 없으면 Service 클래스로 넘어가지 않는다

 

- 지금까지 로그인 폼에서 '회원가입' 클릭시 회원가입 폼으로 넘어가도록 처리함

- 이제 진짜 로그인 기능을 구현하자

- loginform.jsp 에서 아이디, 비밀번호 입력 후 '로그인' 클릭 시 콘솔창

- Controller 클래스가서 doPost() 를 실행했음을 알 수 있다.

 

앞으로 해야할 일

1. Service 클래스 명도 맞추서 Login.java 파일로 하자

2. Controller 클래스에서 "/Login.do" 인 경우를 Login.java 로 보내는 것을 처리하자

3. Service 와 DAO 부분 구현

 

- 로그인 기능 Service 클래스인 Login.java 파일 생성부터 하자

 

회원관리 프로그램 : 로그인 Service 클래스

- 실제 로그인 요청을 처리하는 Login.java 파일을 만들자

- 로그인 폼의 요청이름값과 동일하게 설정

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

- Login.java (수정 전, 한글 인코딩 전, 한글 인코딩 빼고는 다 작성함)

package service;

import java.io.PrintWriter;

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

import dao.MemberDAO;

public class Login implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Login");
		
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.memberAuth(id, passwd); // 회원인증
		if(result == 1) System.out.println("회원인증 성공");
		
		if(result == 1) {	// 회원인증 성공
			session.setAttribute("id", id);
		} else {	// 회원인증 실패
			out.println("<script>");
			out.println("alert('로그인 실패');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/main.jsp");
		
		return forward;
	}

}

<로그인 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어왔다

- 매개변수를 통해 Controller 클래스의 request 객체가 그대로 넘어왔으므로, 폼에서 Controller 클래스로 넘겨준 form 의 각종 입력양식의 값들을 여기 Service 클래스에서 request.getParamter() 로 가져올 수 있다

- 필드값을 설정한 ActionForward 객체를 execute() 를 호출한 Controller 클래스에 돌려줌

 

<로그인 Service 클래스 작성 순서>

1. 먼저 post 방식으로 한글값이 넘어올 수 있으므로 한글값 인코딩을 설정한다

2. 회원 인증 성공시 공유설정을 해야하므로 session 객체 생성, out 객체도 생성해주자

3. 로그인 폼에서 전달한 id, passwd 를 받아온다

- 다음으로는 이 id, passwd 와 DB의 id, passwd 가 같으면 공유설정해야함

4. DAO 객체를 생성하고 회원 인증 select 를 하는 DAO 의 memberAuth() 메소드 호출

- 매개변수로는 id, passwd 를 전달

- 회원 인증에 성공시 결과값을 1로 받기로 하자

5. 회원 인증 성공시 "id" 를 네임값으로 회원의 id 를 공유 설정을 한다

- 파일을 계속 넘어가도 session 값은 유지됨

6. 회원 인증 실패시 out 객체를 통해 실패 메세지를 뿌린다

- 실패시 알림창을 띄우고 이전 페이지로 돌아간다

- return null 이 없으면 콘솔창에 에러가 뜬다, return null 을 통해 이 메소드를 완전히 빠져나가며 아래 내용 실행 안함

- out 객체를 닫아줌

- 회원 인증 성공시, return null 로 빠져나가지 않고 아래 코드가 실행됨

7. 회원 인증 성공시 Service -> 다시 execute() 를 호출한 Controller 로 돌아가야한다

- 이때 Controller 로 ActionForward 객체를 리턴해야한다, ActionForward 객체 forward 생성

- 리턴하기 전에 어떤 방식으로 포워딩할지, 어느 파일로 포워딩할지를 setRedirect(), setPath() 로 설정

- 지금은 request 객체로 공유를 설정하지 않았으므로 dispatcher, redirect 두개의 포워딩 방식 모두 사용 가능, 현재는 dispatcher 방식으로 포워딩하자

- setPath() 메소드 안에는 WebContent 폴더를 기준으로 하위 폴더를 쓰면서 포워딩할 파일명을 적어주기

- 회원가입이 끝나면 main 페이지로 갈 것이므로 main.jsp 로 포워딩하자

8. 이후 DAO 클래스를 작성 해야함

 

+ out 객체와 session 객체 만들기

		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();

- DAO 클래스로 가서 회원 인증 (로그인) 을 해주는 memberAuth() 메소드를 작성하자

 

DAO 클래스 회원인증 (로그인) 기능 메소드 작성

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

- MemberDAO.java 추가된 memberAuth() 부분 코드만

	// 로그인 (회원인증)
	public int memberAuth(String id, String passwd) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ? and passwd=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, passwd);
			rs = pstmt.executeQuery();	//SQL문 실행
			
			if(rs.next()) {	// 회원인증 성공
				result = 1;
			} else {		// 회원인증 실패
				result = -1;
			}
			
		} 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 * from member2 where id =? and passwd = ? 에서 결과를 가져온다는 것은 아이디, 비번이 모두 일치하는 결과가 있다는 의미

- 회원 인증 성공시 1 리턴, 실패시 -1 리턴

- 이후 이 메소드를 호출한 Service 클래스로 이동 -> Controller 클래스로 이동 -> main.jsp 로 포워딩하게 된다

- Service 클래스 -> Controller 클래스로 갈때 forward 객체를 리턴했음

- Controller 클래스에서 main.jsp 로 포워딩 한다

 

- 아직 Controller 클래스에서 "/Login.do" 가 요청이름값일때의 Login.java 로 보내는 처리를 하지 않았음

- Controller 클래스에서 Service 클래스로 연결하는 것은 

 

Controller 클래스에서 로그인 Service 클래스로 가기

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

- MemberController.java 부분

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

		// 로그인 (회원 인증)
		} else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		}

 

- 이제 모든게 연결되었으므로 loginform.jsp 에서 로그인을 시도해보자

- 맞는 비밀번호 입력시

- 아직 main.jsp 가 만들어지지 않았으므로 오류 발생

- 콘솔창을 보면 로그인 폼 -> Controller 클래스("post" 출력) -> Service 클래스("Login" 출력) -> DAO 에서 DB 연결 -> Service 클래스("회원가입 성공" 출력) -> Controller 클래스 -> main.jsp 로 포워딩 잘 되었음을 확인 가능

- 틀린 비밀번호 입력시

- 로그인 실패 라는 메세지 대신 깨진 한글값이 나온다

 

깨진 한글값 문제 처리

- Login.java 파일에서 회원가입 실패시 출력하는 메세지가 한글이 깨져서 나타난다

- 위에서 "한글값" 을 인코딩하는 setCharacterEncoding("utf-8") 은 있지만 현재 문서의 한글 인코딩은 안했다

- 그래서 alert 로 출력되는 한글이 깨졌던 것

- JSP 에서는 자동으로 생성되었던 현재 문서의 한글을 인코딩하는 코드

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

- 이 코드를 JSP 가 아니라 Java 클래스 내에서 JAVA 에 맞게 작성해야함

		response.setContentType("text/html; charset=utf-8");

- 이 한줄을 Login.java 에 추가해야함

 

+ 이 코드는 한글값 인코딩, 한글 문서 인코딩과 관련없다

		request.setCharacterEncoding("utf-8");

 

- Login.java (수정 후, 한글 인코딩 완료)

package service;

import java.io.PrintWriter;

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

import dao.MemberDAO;

public class Login implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Login");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.memberAuth(id, passwd); // 회원인증
		if(result == 1) System.out.println("회원인증 성공");
		
		if(result == 1) {	// 회원인증 성공
			session.setAttribute("id", id);
		} else {	// 회원인증 실패
			out.println("<script>");
			out.println("alert('로그인 실패');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/main.jsp");
		
		return forward;
	}
}

 

- 다시 loginform.jsp 를 실행하고 틀린 아이디나 비밀번호를 입력 후 '로그인' 을 클릭하자

- 문서의 한글 인코딩이 잘 처리 되었다!

 

- 맞는 비밀번호를 입력했을때도 한글이 깨지는 문제 해결됨

- main.jsp 파일이 아직 생성되지 않아서 아직 404 에러가 뜬다

 

중간 정리

+ 현재 공유된 값 : 로그인 성공시 session 으로 공유된 id 값

- 그래도 main.jsp 로 포워딩하는 것까지 Controller 클래스에서 다 처리했다

- Service 클래스를 거쳐서 돌아올때는 forward 객체가 null 이 아니므로 조건식을 만족해서 포워딩함

- session 으로 공유를 설정할때는 어느 방식으로 포워딩해도 상관없다

- 지금 로그인 페이지는 session 으로만 공유설정했으므로 상관없음, 나중에 request 객체로 공유 설정시에는 Dispatcher 방식으로 포워딩 해야함

 

- main.jsp 파일에서는 세션 유무에 따라 보이는 화면이 달라진다


회원관리 프로그램 : 중간 페이지 main.jsp

세션 유무(로그인 성공 유무) 에 따라 다른 화면 보여주기

- WebContent/member 폴더 하위에 main.jsp 파일 생성 및 작성

- main.jsp (수정 전 1)

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

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">
	회원가입 <br>
	로그인 <br>
</c:if>

- EL / JSTL 사용하기

- JSTL 코어 라이브러리를 불러온다

- main.jsp 파일에서는 세션 유무에 따라 보이는 화면이 달라진다

- session 값이 있는경우, 없는 경우 두가지 경우로 나눠서 처리, JSTL 의 if 태그 사용

- 조건식은 EL 로 만들고 sessionScope 내장객체를 사용함, sessionScope.id 는 session 으로 공유된 네임 "id" 의 값 의미

 

- 세션 값이 있을때 (즉, 로그인 (회원인증) 하고 main.jsp 로 왔을때)

- 세션 값이 없을때 (바로 main.jsp 를 그냥 실행했을때)

 

- 이제 세션이 없는 경우 나타나는 텍스트인 '회원가입' 과 '로그인' 에 링크걸기

- main.jsp (수정 전 2)

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

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

<회원가입 링크>

- href = "./MemberForm.do" 만을 설정시 못 찾아간다

- 앞에 프로젝트 명을 붙여서 이동시켜줘야함

- 콘솔창

<로그인 링크>

- 아직 Controller 클래스에 로그인 폼으로 넘겨주는 코드는 없다

- 요청이름값을 "LoginForm.do" 로 하자

- LoginForm.do 로 요청 했으므로 Controller 클래스로 찾아가긴 했다

- 마찬가지로 프로젝트 명을 앞에 써주기 위해 request.getContextPath() 사용

- Controller 클래스에서 /LoginForm.do 로 올때를 처리해주기

- 폼이 아니므로 무조건 Get 방식으로 전달한다,

- 콘솔창

- command 변수에 요청 이름값이 저장되어있다

- Controller 클래스에서 command 가 LoginForm.do 일때 로그인 폼으로 가는 코드를 작성

 

Controller 클래스에서 로그인 폼으로 가기

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

- MemberController.java 부분

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

		// 로그인 폼	
		} else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/loginform.jsp");
		}

- 지금은 DB연동을 안하므로 Service 클래스로 갈 필요 없다

- 회원가입 폼으로 가는 것과 마찬가지로 처리한다

- ActionForward 객체 forward 를 생성후 바로 값을 세팅해줌

- View 페이지는 member 폴더 하위 loginform.jsp 이다

- dispatcher 방식으로 포워딩 한다

 

- 그 후에 아래의 포워딩 코드에서 forward != null 을 만족하므로 포워딩한다

 

- main.jsp 실행 후 '로그인' 누르기

+ 세션값이 없으므로 아래 화면이 뜬다

- 로그인 포믕로 잘 이동했음을 확인 가능

 

Model 1 vs Model 2

- Model 1 에서는 Loginform.jsp 로 바로 갈 수 있다

- Model 2 에서는 Controller 로 갔다가 다시 View (Loginform.jsp) 로 가도록 설정해야한다

 

- 다시 main.jsp 를 보자

- sessionScope.id 는 EL 내장객체, session.getAttribute("id") 를 대신한다

- sessionScope.id 대신 다른 방법이 있다, empty 연산자

- 위의 if 조건식을 empty 연산자로 바꿔서 처리해보자

 

empty 연산자

- '비어있는지', '없는지' 를 묻는 EL연산자

- 비어있으면 true, 비어있지 않으면 false 값이 된다

 

empty 연산자 코드로 바꾸기 1

- main.jsp 의 '세션이 있는 경우' if 태그 코드를 empty 연산자 코드로 바꾸기

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">

- 아래 코드로 바꿀 수 있다

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">

- empty 연산자는 없으면 true 이므로 세션이 있는 경우는 ! 를 앞에 붙여야함

- empty 연산자는 == null 과 같은 의미, '없으면' 의 의미이다

 

empty 연산자 코드로 바꾸기2

- 마찬가지로 main.jsp 의 '세션이 없는 경우' if 태그 아래 코드는 이렇게 바꾼다

<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">

- 아래코드로 바꾼다

<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">

- 또한 이렇게 sessionScope 를 빼고 id 만 적어도 똑같이 처리된다

<!-- 세션이 없는 경우 -->
<c:if test="${empty id }">

- 똑같이 처리된다

- sessionScope 를 적는게 원칙이지만 빼도 처리된다

 

- main.jsp (수정 전 3)

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

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

회원관리 프로그램 : 로그아웃

- main.jsp 세션있는 경우에서 나타나는 차에서 '로그아웃' 을 눌렀을때 로그아웃을 수행하는 기능을 만들자

 

- main.jsp 에서 링크를 연결하자

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

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	<a href="./Logout.do">로그아웃</a> <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 수정한 부분 코드만 캡처

- 요청 이름값은 "/Logout.do" 로 하자

- 로그아웃 처리는 DB연동을 안하므로 Service 클래스로 갈 필요 없음

- 로그인 하고 main.jsp 로 와서 '로그아웃' 을 눌러보자

- command 에 요청 이름값 "/Logout.do" 이 출력되고 있다

- Controller 클래스의 doGet(), doProcess() 메소드 안까지 잘 찾아 간 것

 

- Controller 클래스로 가서 요청이 "/Logout.do" 로 온 경우에 View 페이지로 넘겨주는 처리를 하자

 

Controller 클래스에서 회원가입 폼으로 가기

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

- MemberController.java 부분

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

		// 로그아웃
		} else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/logout.jsp");
		}

- 지금은 DB연동 안하므로 Service 클래스 갈 필요 없다

- 로그아웃 View 페이지인 logout.jsp 로 보내주기만 하면 된다.

- 그래서 회원가입 폼으로 갈 때와 비슷하게 처리한다

- ActionForward 객체 forward 를 생성하고 바로 값을 설정함

 

- 이제 member 폴더 하위에 logout.jsp 파일을 생성하자

- logout.jsp

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

<%
	// 세션 삭제
	session.invalidate();
%>

<script>
	alert("로그 아웃");
	location.href="./LoginForm.do"
</script>

logout.jsp 에서 해야할 일

1. session 삭제

- 브라우저 창이 닫히지 않아도 강제로 session 끊기

- session.invalidate() 메소드 사용

2. 로그아웃 되었다는 메세지를 뿌린 후 다시 로그인 폼으로 가기(포워딩)

- 로그인 폼으로 가는 요청이름인 "/LoginForm" 을 사용해서 로그인 폼으로 간다

 

- 로그인 폼에서 로그인 한 후 main.jsp 에서 '로그아웃' 클릭하자

- 로그인 폼으로 잘 넘어간다.

- Controller 클래스에서 View 페이지로 포워딩되었다

+ 그 상태에서 main.jsp 실행시 이 화면이 뜨는걸 보아 세션이 삭제되었다

- 콘솔창

- 로그아웃 기능 구현 완료

 

다음에 만들 Model 2 게시판 프로그램

- 이후에 만들 프로그램

- request 객체로 공유를 많이 한다

- lib 폴더에 라이브러리를 저장하는 대신 maven 을 사용한다

복습

Model 2 전체 흐름 정리

- Controller 클래스가 클라이언트의 요청을 받음, WebServlet 어노테이션으로 Controller 클래스로 찾아감

- Service 클래스는 여러개를 만드는데 공통적인 내용을 부모 인터페이스로 만들고 그것을 상속받아서 사용함

- DAO 클래스에서 검색한 결과를 View 페이지에 출력할땐, select 를 수행후 결과를 Service 클래스로 다시 돌아가고, View 페이지에 결과를 출력

- 돌려줄 값이 많을때는 Service 클래스에서 돌려줄 값을 request 객체로 공유 설정 해야한다.

- 공유 설정 후 포워딩 함, 포워딩 된 JSP 파일에서 공유된 값을 가져와서 표현 언어로 출력


JSTL (JSP Standard Tag Library)

- 널리 사용되는 커스텀 태그를 표준으로 만든 태그 라이브러리

- 태그 중 일종, 사용자 정의 태그의 표준

- View 페이지에서 결과를 출력할때 EL, JSTL 이 같이 사용된다.

- 라이브러리가 필요하다

* 사용자 정의 태그 = 개발자가 만들어 쓰는 태그

 

JSTL 환경 구축

- JSTL 을 쓰기 위해서는 라이브러리가 필요하므로 가장 먼저 라이브러리를 구한다.

 

JSTL 라이브러리 구하기

https://tomcat.apache.org/taglibs/standard/

 

Apache Taglibs - Apache Standard Taglib: JSP[tm] Standard Tag Library (JSTL) implementations

<!-- Copyright 1999-2011 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/

tomcat.apache.org

- JSTL 1.2 버전을 다운로드

- 해당 프로젝트의 WebContent/WEB-INF/lib 폴더에 jstl-1.2.jar 를 저장한다

 

JSTL 라이브러리

- 5개 종류의 JSTL 라이브러리가 있다

- prefix 에 접두어를 쓰면 사용할 수 있다.

- 이 라이브러리를 관리하는 기관의 URI 주소를 써야한다, 주소를 설정해야 그 라이브러리를 불러온다는 의미

+ JSP 태그 중 사용자 정의 태그이다.

 

JSTL 예제

실습 준비

- 한글이름은 문제가 생길수 있으므로 '사용자 정의태그(JSTL)' 을 'JSTL' 로 이름 수정

- JSTL/lib/jstl-1.2.jar 파일을 WEB-INF/lib 안으로 복사

 

JSTL 예시

- JSTL/Core/ex1/setTag.jsp

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

<c:set var="num1" value="${20}" />
<c:set var="num2">
10.5
</c:set>
<c:set var="today" value="<%= new java.util.Date() %>" />

<html>
	<head>
		<title>set 태그와 remove 태그</title>
	</head>

	<body>
		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

		<c:remove var="num1" scope="page" />

		<p>
		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}
	</body>
</html>

 

라이브러리 불러오기 (setTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

- 라이브러리는 5개 종류가 있다

- 그 중 하나인 코어 라이브러리, 이때 라이브러리를 불러오는 형식

- 어떤 라이브러리를 사용하냐에 따라 prefix 가 정해져 있다

- 코어 라이브러리를 쓸때는 접두어를 "c" 를 쓴다

- 또한 코어에 해당하는 uri 값을 불러온다

- 이 코드가 JSP의 사용자 정의 태그(tag libary tag) 이다.

 

JSTL 지원 라이브러리

라이브러리 하위 기능 접두어 관련 URI
코어 변수 지원
흐름 제어
URL 처리
c http://java.sun.com/jsp/jstl/core
XML XML 코어
흐름 제어
XML 변환
x http://java.sun.com/jsp/jstl/xml
국제화 지역
메세지 형식
숫자 및 날짜 형식
fmt http://java.sun.com/jsp/jstl/fmt
데이터베이스 SQL sql http://java.sun.com/jsp/jstl/sql
함수 콜렉션 처리
String 처리
fn http://java.sun.com/jsp/jstl/functions

- 90% 이상 코어 라이브러리만 사용, 10% 는 국제화 라이브러리 사용

- 코어 라이브러리는 preifx값을 "c" 라고 쓰고 코어 라이브러리를 관리하는 기관 uri 를 쓴다

- 코어 라이브러리의 주요 기능은 변수 생성, 변수 삭제, 조건식, 반복식, URL 처리

- 국제화 라이브러리는 주로 날짜 시간을 년월일시분초 등의 패턴을 지정할때 사용된다, Model 2에서 SimpleDateFormat 의 역할

 

JSP 라이브러리 : 코어 라이브러리

코어 라이브러리 지원 태그

- set 태그 : 변수를 정의(생성)할때 사용

- remove 태그 : 변수를 제거할때 사용

- if 태그 : if문과 유사

- choose - when - otherwise 태그 : JSLT 에선 if-else 가 없으므로 if-else 처럼 쓰고 싶을때 사용

- forEach 태그 : for문과 유사

 

태그 사용법

set 태그 사용법

- 태그들이 어떤 라이브러리에 포함되어있냐에 따라 접두어를 가장 먼저 쓴다

ex) set 태그는 코어 라이브러리이므로 접두어 c 를 쓴다

- 각 태그마다의 속성을 알아야 한다

 

JSTL 예제 1

코어 라이브러리

set 태그 / EL 출력

- JSTL/Core/ex1/setTag.jsp (중복)

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

<c:set var="num1" value="${20}" />
<c:set var="num2">
10.5
</c:set>
<c:set var="today" value="<%= new java.util.Date() %>" />

<html>
	<head>
		<title>set 태그와 remove 태그</title>
	</head>

	<body>
		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

		<c:remove var="num1" scope="page" />

		<p>
		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}
	</body>
</html>

- 한글 인코딩을 가장 위에서 하고 있다

- 사용자 정의 태그로 코어(Core) 라이브러리를 불러옴

 

코어 라이브러리 불러오기 (setTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

- JSTL 태그를 쓰기 위해서는 그 태그가 포함된 라이브러리를 먼저 불러와야한다

- 이 코드가 없으면 에러는 없지만 변수 값 등이 출력이 되지 않음

- 해당 라이브러리의 uri 를 모르면 Ctrl + Space 중 선택한다

 

태그 사용하기 (setTag.jsp 부분)

- 태그앞에 접두어를 붙여야한다

 

set 태그 : 변수 정의 (setTag.jsp 부분)

<c:set var="num1" value="${20}" />

- num1 변수를 정의하면서 num1 에 20 을 할당하고 있다

- 아래와 똑같다

<c:set var="num1" value="20" />

- 또는 set 태그 사이에 값을 입력해도 값이 저장된다.

<c:set var="num2">
10.5
</c:set>

- 변수 선언 하고 객체를 저장할 수도 있다

<c:set var="today" value="<%= new java.util.Date() %>" />

- today 변수에는 Date 객체가 저장되었다

 

변수 값 출력 (setTag.jsp 부분)

		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

- 출력시 JSTL 과 EL 이 같이 쓰인다 , 지금은 EL 로 출력하고 있다.

- 첫 줄은 num1 변수안의 값을 출력하고 있다

- 일반적인 JSP 변수를 ${} (EL) 안에 쓰면 출력되지 않지만 JSTL 로 정의된 변수를 ${} (EL)안에 쓰면 출력된다

- num1, num2, today 는 JSTL 로 정의된 변수이므로 ${} (EL) 안에서 출력된다

- today 변수 출력시 영미권 날짜 시간 형식으로 출력된다.
- JSTL 로 정의된 변수는 표현식 태그로 출력할 수 없고, EL 로 출력해야한다

+ EL 안 에서 산술연산을 포함한 각종 연산이 된다.

 

변수 삭제 (제거) (setTag.jsp 부분)

		<c:remove var="num1" scope="page" />

- remove 태그는 코어(Core) 라이브러리에 포함된 라이브러리 이므로 접두어 쓰기

- var = "삭제할 변수명" scope ="4가지 영역 중 하나"

		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}

- num1 이 삭제되었으므로 아무값도 나타나지 않음, 오류는 발생하지 않고 나타나지 않음

- num1 이 삭제되더라도 num1 + num2 에서 오류가 발생하는 것이 아니라 num2 값만 나옴

 

- 실행 결과 캡처


EL 에서 출력 가능한 변수

- 일반적인 변수는 ${} 에서 출력불가

- 일반적인 변수더라도 request 로 공유한 변수는 ${} 에서 출력 가능

- JSTL 로 정의한 변수는 ${} 에서 출력 가능

+ JSTL 로 정의한 변수는 EL 태그로만 출력 가능하다!

 

1. 일반적인 JSP 변수를 EL 로 출력시 출력되지 않음

		<% 
			String str = "JSP변수";
		%>
		변수 : str1 = <%=str %> <br> <!-- "JSP변수" 출력 -->
		변수 : str2 = ${str} <br> <!-- 출력 안됨 -->

- 출력되지 않음, 오류가 발생하진 않음

 

2. 일반적인 JSP 변수를 EL 로 출력하고 싶으면 공유 설정해야함

		<% 
			String str = "JSP변수";
			request.setAttribute("st", str);	// 공유 설정
		%>
		변수1 : str1 = <%=str %> <br> <!-- "JSP변수" 출력 -->
		변수2 : str2 = ${str} <br> <!-- 출력 안됨 -->
		변수3 : str3 = ${st} <br> <!-- "JSP변수" 출력-->

- 현재는 현재 페이지에서 공유 설정하므로 page 부터 모든 4개 영역 공유 설정해도 유효 영역이므로 EL 로 출력 가능

- 지금은 현재 request 로 공유 설정

- 공유된 값은 EL로 출력됨

 

+ EL을 쓰지 않고 str3 와 같은 결과를 출력하려면?

- 아래는 EL 태그를 써서 간략하게 공유된 값을 출력하고 있다

		변수3 : str3 = ${st} <br> <!-- "JSP변수" 출력-->

- EL 태그를 쓰지 않고 이것과 같은 코드는 아래

		<%
			String s = (String) request.getAttribute("st");
		%>
		변수4 : str4 = <%=s %> <br>	<!-- "JSP변수" 출력 -->

- EL 태그를 쓰지 않고 공유된 값을 출력하고 싶으면 먼저 getAttribute() 로 다운캐스팅해서 변수에 저장

- 이후 그 변수를 표현식 태그로 출력

+ getAttribute() 는 Object 를 돌려주므로 안의 값의 자료형인 String 형으로 다운캐스팅 명시해야함

3. JSTL set 태그로 정의된 변수

<c:set var="num1" value="${20}" />

- JSTL set 태그로 정의된 변수 num1

		변수 num1 = ${num1} <br>

- EL 태그로 출력 가능하다

 

EL 로 출력가능 한 것

1. set 태그로 정의한 변수

2. 현재 공유 설정된 변수

- 이 두가지 밖에 없다


if 태그

- if문과 같은 역할

- JSTL 에는 if 태그만 있다, if-else, if-else if 없음

- 형식 :

- 조건식이 참이면 if 태그 안의 내용이 실행되고, 조건식이 거짓이면 if 태그 안의 내용이 실행되지 않음

 

JSTL 예제 2

코어 라이브러리

if 태그

- JSTL/Core/ex2/if/ifTagForm.jsp

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

<html>	
	<body>
	<form method=post action=ifTag.jsp>
	이름 <input type=text name=name><br>
	나이<input type=text name=age><br>
	<input type=submit value="전송">
	</form>
	</body>
</html>

 

- action으로 지정된 ifTag.jsp 파일을 생성하자

- JSTL/Core/ex2/if/ifTag.jsp

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

<c:if test="true">
	무조건 실행 <br>
</c:if>

<c:if test="${param.name == 'toto'}">
	당신의 이름은 ${param.name } 입니다. <br>
</c:if>

<c:if test="${param.age >= 20}">
	당신의 나이는 20세 이상 입니다.
</c:if>

- EL 에선 request.getParamter() 대신 객체 param 을 사용 가능

 

taglib 추가 : 코어 라이브러리 추가 (ifTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

 

if 태그 사용 1 (ifTag.jsp 부분)

<c:if test="true">
	무조건 실행 <br>
</c:if>

 

if 태그 사용 2 / EL 객체 param (ifTag.jsp 부분)

<c:if test="${param.name == 'toto'}">
	당신의 이름은 ${param.name } 입니다. <br>
</c:if>

- param 은 EL 에 쓸 수 있는 객체

- test 속성은 바깥쪽에 " ", 안쪽엔 ' ' 를 써야함

- EL에서는 equals() 가 아닌 비교연산자 == 를 써야함

- EL로 조건식을 쓰고 있다

- param.name 은 request.getParameter("name") 과 같다

- 즉 이름 입력양식에 사용자가 'toto' 를 입력한 경우에만 아래의 문장이 출력된다.

 

if 태그 사용 3 / EL 객체 param (ifTag.jsp 부분)

<c:if test="${param.age >= 20}">
	당신의 나이는 20세 이상 입니다.
</c:if>

- param.name 은 request.getParameter("age") 과 같다

- EL 안에서 비교연산자를 사용할 수 있다

- 나이 입력 양식에 20 이상의 값을 입력한 경우에 if 태그 안의 문장이 실행되어 아래처럼 출력됨


choose - when - otherwise 태그

- if만 있고 if-else 나 if-else if 기능을 하는 태그는 없다

- 다만 if-else if 와 비슷한 역할을 수행하는 태그가 있다, 조건식을 여러개 쓸 때 사용하는 태그가 있다

- choose - when - otherwise 태그를 써서 조건식을 여러개 쓸 수 있다

- switch-case 문이나 if-else if문과 비슷

- 형식 : 

- 조건식을 쓸 때는 if 태그가 아닌 when 태그로 조건식을 만듬, if 대신 when 이 사용됨

- 조건식이 참이면 when 태그 안의 내용을 실행함

- when 태그를 여러개 써서 조건식을 여러개 사용한다

- 맨 마지막에 쓰이는 otherwise 태그는 switch-case문의 default 와 같은 역할, otherwise는 쓰지 않아도 됨

 

choose - when - otherwise 태그 주의

- 조건식들이 여러개 오는 경우 여러개 조건식을 모두 만족한다고 다 실행되지 않음

- if-else if 문과 마찬가지로 가장 먼저 만족하는 위의 조건 태그 안의 내용 1개만 실행하고 choose 태그를 빠져나감

- 즉, if 문이 여러개 있는게 아니라 if-else if 문과 같은 방식으로 동작

- 위의 조건을 다 만족하지 않을때는 otherwise 태그 안의 내용을 실행

 

JSTL 예제 3

코어 라이브러리

choose - when - otherwise 태그

- JSTL/Core/ex3/choose/ifTagForm.jsp

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

<html>	
	<body>
	<form method=post action=chooseTag.jsp>
	이름 <input type=text name=name><br>
	나이<input type=text name=age><br>
	<input type=submit value="전송">
	</form>
	</body>
</html>

 

- action으로 지정된 chooseTag.jsp 파일을 생성하자

 

- JSTL/Core/ex3/choose/chooseTag.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>Insert title here</title>
</head>
<body>

<ul>
	<c:choose>
		<c:when test="${param.name == 'toto'}">
			<li>당신의 이름은 ${param.name } 입니다.</li>
		</c:when>
		<c:when test="${param.age >= 20}">
			<li>당신의 나이는 20세 이상 입니다.</li>
		</c:when>
		<c:otherwise>
			<li>당신의 이름은 'toto'가 아니고, 나이는 20세 이상이 아닙니다.</li>
		</c:otherwise>
	</c:choose>
</ul>

</body>
</html>

- EL 로 조건식을 쓰고 있다

- 필요하면 when 태그를 계속 추가해서 조건식을 추가하면 된다

- if-else if 처럼 가장 먼저 만족한 조건의 내용을 실행하고 빠져나감

- 마지막은 otherwise 태그로 끝난다,어느 when 태그의 조건식도 만족하지 않으면 실행됨

 

- 모든 when 태그를 만족할 때

- 모든 2개의 when 조건절을 만족하지만 가장 위의 조건을 만족하고, 빠져나가기 때문에 첫번째 when 태그 안의 내용만 실행됨

 

- 모든 when 태그를 만족하지 않을때

- otherwise 태그 안의 내용이 실행됨


JSTL 예제 4

코어 라이브러리

catch 태그, out 태그

- JSTL/Core/ex33/jstl_core_ex1.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL core 라이브러리 사용 예제 1</title>
</head>
<body>
<c:set var="test" value="Hello JSTL!"/>
<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>
<h3>&lt;c:set&gt; 사용 후 : ${test}</h3>

<c:remove var="test"/>
<h3>&lt;c:remove&gt; 사용 후 : <c:out value="${test}"/></h3>

<c:catch var="err">
<%=10/0%>
</c:catch>
<h3>&lt;c:catch&gt;로 잡아낸 오류 : <c:out value="${err}"/></h3>

<c:if test="${5<10}">
<h3>5는 10보다 작다.</h3>
</c:if>
<c:if test="${6+3==9}">
<h3>6 + 3 은 9이다.</h3>
</c:if>

<c:choose>
	<c:when test="${5+10==50}">
		<h3>5+10은 50이다.</h3>
	</c:when>
	
	<c:otherwise>
		<h3>5+10은 50이 아니다.</h3>
	</c:otherwise>
</c:choose>
</body>
</html>

- 코어 라이브러리를 불러오고 있다

- body 태그 안에서 set 태그로 test 란 변수 정의

 

< , > 출력 (jstl_core_ex1.jsp 부분)

<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>

- 그냥 < 나 > 를 쓰면 동작하므로 부등호기호를 만들어서 <, > 를 출력하기 위해서 사용

- $lt; 는 '<' 를 만들어줌 (less than)

- $gt; 는 '>' 를 만들어줌 (greater than)

 

out 태그로 브라우저 출력 (jstl_core_ex1.jsp 부분)

<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>

- EL로 출력시켜도 되지만 이렇게 JSTL out 태그로 브라우저에 출력시켜도 된다

- value 속성값으로 출력시킬 값을 적는다

 

- 아래와 같은 역할

<h3>&lt;c:set&gt; 사용 후 : ${test}</h3>

- 변수 test는 JSTL set 태그로 정의한 변수 이므로 EL을 써서도 출력 가능

 

+ remove 태그로 test 변수 삭제 후 out 객체로 출력시 아무런 값이 출력되지 않음

<c:remove var="test"/>
<h3>&lt;c:remove&gt; 사용 후 : <c:out value="${test}"/></h3>

 

catch 태그로 예외처리 (jstl_core_ex1.jsp 부분)

<c:catch var="err">
<%=10/0%>
</c:catch>
<h3>&lt;c:catch&gt;로 잡아낸 오류 : <c:out value="${err}"/></h3>

- 0 으로 나눌때 발생하는 아리스매틱 예외를 catch 태그를 사용해서 err 변수로 잡는다

- catch 태그 안에 예외가 발생할 수 있는 가능성이 있는 문장을 넣음

- 그 err 변수를 출력하면 어떤 예외가 발생했는지 확인 가능

 

- 전체 출력 캡처


forEach 태그

- for문과 같은 역할을 하는 태그

- 크게 2가지 형식이 있다

- 첫번째 형식 :

- var 속성으로 forEach 에서 사용할 변수 지정

- begin 속성으로 시작값을 지정, end 속성으로 끝 값을 지정

- step 속성으로 증가치 설정, step 속성 생략시 기본 증가치는 1

- 반드시 값 좌우에는 " " 가 있어야 정상 동작한다, 숫자더라도 속성값은 " " 붙여야함 

- 반복문이 돌아가며 forEach 태그 안의 내용이 출력됨

 

- 두번째 형식 : 

- items 속성값으로는 순차적인 자료구조가 온다. ex) 배열, 리스트

- 이 배열, 리스트를 forEach문 바깥 위에서 정의하고 바로 itmes 자리에 들어가면 안됨

- 배열과 리스트를 먼저 request 객체로 공유하고, 공유한 값이 이 items 자리에 들어가야한다

- 또는 set 태그로 정의된 배열, 변수명 등도 items 속성값 자리에 올 수 있다

 

- 두가지 형식이 모두 자주 사용됨

 

JSTL 예제 5

코어 라이브러리

forEach 태그

- JSTL/Core/ex4/forEach/forEachTag.jsp

<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
    java.util.HashMap mapData = new java.util.HashMap();
    mapData.put("name", "최범균");
    mapData.put("today", new java.util.Date());
%>
<c:set var="intArray" value="<%= new int[] {1,2,3,4,5} %>" />
<c:set var="map" value="<%= mapData %>" />
<html>
<head><title>forEach 태그</title></head>
<body>
<h4>1부터 100까지 홀수의 합</h4>
<c:set var="sum" value="0" />
<c:forEach var="i" begin="1" end="100" step="2">
<c:set var="sum" value="${sum + i}" />
</c:forEach>
결과 = ${sum}

<h4>구구단: 4단</h4>
<ul>
<c:forEach var="i" begin="1" end="9">
   <li>4 * ${i} = ${4 * i}
</c:forEach>
</ul>

<h4>int형 배열</h4>

<c:forEach var="i" items="${intArray}" begin="2" end="4">
    [${i}]
</c:forEach>

<h4>Map</h4>

<c:forEach var="i" items="${map}">
    ${i.key} = ${i.value}<br>
</c:forEach>
<br><br>

<%
	List list = new ArrayList();
	list.add("자바");
	list.add("웹표준");
	list.add("JSP");
	list.add("오라클");
	list.add("스프링");
	list.add("파이썬");
	list.add("텐서플로우");
	
	request.setAttribute("slist",list);	// 공유설정
%>
<!-- 방법1. -->
<c:forEach var="s" items="${slist}">
	${s} <br>
</c:forEach>
<br><br>

<%
	// 기존 코드로 처리
	List li = (List) request.getAttribute("slist");
	for(int i=0; i<li.size(); i++) {
		String str = (String) li.get(i);
		out.println(str + "<br>");
	}
%>
<br><br>

<!-- 방법2. -->
<c:set var="s1" value="<%=list %>"/>
<c:forEach var="s2" items="${s1}">
	${s2} <br>
</c:forEach>

</body>
</html>

- 코어 라이브러리를 taglib 로 불러옴

- 스크립틀릿 태그로 Map 객체를 생성하고, 키, 밸류 쌍으로 2개의 데이터를 넣음

ex) 키 "name", 밸류 "최범균"

- 1 부터 5까지 원소를 가진 int 형 배열을 가지고 있는 변수 intArray 정의

- Map 객체 mapData 를 가지고 있는 변수 map 정의 

 

forEach 태그 사용 1 / forEach 첫번째 형식 (forEachTag.jsp 부분)

<h4>1부터 100까지 홀수의 합</h4>
<c:set var="sum" value="0" />
<c:forEach var="i" begin="1" end="100" step="2">
<c:set var="sum" value="${sum + i}" />
</c:forEach>
결과 = ${sum}

- set 태그로 합을 저장할 변수 sum 을 정의하고 누적에 영향을 주지 않는 0 으로 초기화

- forEach 태그에서 루프를 돌릴 변수 i , 시작값은 1, 끝값은 100, 증가치는 2, 홀수만 누적되는 반복문임

- sum 변수에 누적시키기 위해 sum 변수를 set 태그로 다시 정의하고 sum + i 로 값 누적, sum = sum + i 와 같다


forEach 태그 사용 2 / forEach 첫번째 형식 (forEachTag.jsp 부분)

<h4>구구단: 4단</h4>
<ul>
<c:forEach var="i" begin="1" end="9">
   <li>4 * ${i} = ${4 * i}
</c:forEach>
</ul>

- 1 부터 9 까지를 i 변수에 하나씩 전달해서 구구단 4단 출력

- forEach 에서 루프를 돌릴 변수인 i 를 출력할때는 그냥 출력해선 안되고 EL 로 출력해야한다.

 

forEach 태그 사용 3 / forEach 두번째 형식 (forEachTag.jsp 부분)

<h4>int형 배열</h4>

<c:forEach var="i" items="${intArray}" begin="2" end="4">
    [${i}]
</c:forEach>

- items 속성도 쓰고 있고, begin, end 속성도 쓰고 있다

<items 속성>

- items 속성값으로는 배열, 리스트가 주로 들어감

- 바로 배열, 리스트가 들어갈 수는 없고 "set 태그로 정의한 변수"가 들어가든지, "request 로 공유한 변수"가 들어가야함

- 지금은 위에서 "set 태그로 정의한 변수"인 intArray 가 items 로 들어감

- set 태그로 정의되었고, intArray 라는 변수가 배열이므로 items 속성값으로 들어갈 수 있다.

- intArray 에서 하나씩 원소를 가져와서 변수 i 에게 전달한다.

<begin 속성, end 속성>

- begin, end 속성이 없을때는 intArray 배열 안의 원소들을 모두 i 로 넘어가서 출력된다

- begin, end 속성이 있을때는 intArray 배열 안의 원소 중 배열 index 가 2부터 4인 원소가 i 로 넘어간다

 

+ 배열에서 첫번째 인덱스는 0

 

forEach 태그 사용 4 / forEach 두번째 형식 (forEachTag.jsp 부분)

<h4>Map</h4>

<c:forEach var="i" items="${map}">
    ${i.key} = ${i.value}<br>
</c:forEach>

- 맵 객체를 가지고 있는 변수 map 이 items 속성으로 들어갔다, 주로 배열, 리스트가 들어가지만 여기선 맵이 들어갔다

- 맵을 통해서 첫번째 데이터부터 i 로 전달되서 key 와 value 값을 출력

- set 태그로 정의한 변수이므로 items 속성에 들어갈 수 있다

 

forEach 태그 사용 5 / forEach 두번째 형식 (forEachTag.jsp 부분)

 

- List 가 items 속성에 들어갔다

- list 에 들어있는 원소값들을 forEach 태그로 출력시키고 있다

+ 이렇게 List 를 공유설정하고 포워딩 한 다음, 포워딩된 곳에서 foEach로 List 를 출력하는 경우가 많다

 

JSP 에서 정의한 list 를 바로 items 에 넣으면?

- list 를 바로 사용하면 출력되지 않음

 

forEach items 속성 사용 방법 2가지

1. 배열이나 리스트를 request 객체로 공유하고 공유 네임값이 items 자리에 와야한다. (forEachTag.jsp 부분)

<%
	List list = new ArrayList();
	list.add("자바");
	list.add("웹표준");
	list.add("JSP");
	list.add("오라클");
	list.add("스프링");
	list.add("파이썬");
	list.add("텐서플로우");
	
	request.setAttribute("slist",list);	// 공유설정
%>
<!-- 방법1. -->
<c:forEach var="s" items="${slist}">
	${s} <br>
</c:forEach>

- itmes 에 공유 네임값인 "slist" 를 적는다

- slist 로부터 원소가 하나씩 s 로 넘어와서 하나씩 출력된다

- 잘 출력된다.

 

+ 위와 같은 기능, forEach 태그를 쓰지 않는 경우

리스트 안의 모든 원소를 출력하는 것을 기존 코드로 처리시 (forEachTag.jsp 부분)

<%
	// 기존 코드로 처리
	List li = (List) request.getAttribute("slist");
	for(int i=0; i<li.size(); i++) {
		String str = (String) li.get(i);
		out.println(str + "<br>");
	}
%>

- 똑같이 출력된다.

 

2. 배열이나 리스트를 set 태그로 정의한 후, 그변수가 items 자리에 와야 한다. (forEachTag.jsp 부분)

<!-- 방법2. -->
<c:set var="s1" value="<%=list %>"/>
<c:forEach var="s2" items="${s1}">
	${s2} <br>
</c:forEach>

- set 태그로 정의된 변수는 items 속성값으로 올 수 있다

- 잘 출력된다.

 

- 전체 출력 캡처

 

실제 활용

- 목록을 가져오는 Service 클래스에서 목록을 가져오기 위한 6개 변수와 리스트를을 request 객체로 공유설정

- 이후 그 리스트 안 DTO 객체들을 View 페이지에서 출력하기 위해 forEach 문의 items 에 리스트 공유 네임값을 쓴다 


 

forTokens 태그

- 자바의 StringTokenizer 과 비슷

- 특정 구분기호로 파싱함

 

JSTL 예제 6

코어 라이브러리

forTokens 태그

- JSTL/Core/ex5/forTokens/forTokensTag.jsp

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

<html>
<head><title>forTokens 태그</title></head>
<body>

콤마와 점을 구분자로 사용:<br>
<c:forTokens var="token" 
             items="빨강색,주황색.노란색.초록색,파랑색,남색.보라색"
             delims=",.">
${token} 
</c:forTokens>

</body>
</html>

-  코어 라이브러리를 taglib 태그로 불러옴

- forTokens 태그를 사용

- var 속성값 : 값들을 받기 위한 임의의 변수명

- itmes 속성값 : 어떤 문자열이 있다

- delims 속성값 : 구분기호가 들어감, 여러개 구분기호 사용 가능

- 여기서는 , 와 . 으로 잘라서 token 변수로 전달하고, 그걸 EL로 출력한다

+ forEach, forTokens 에서 값들을 받는 변수는 그 태그 사이에서 EL 로 출력함


JSTL 예제 7

코어 라이브러리

forEach 태그, forTokens 태그

- JSTL/Core/ex44/jstl_core_ex2.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL core 라이브러리 사용 예제 2</title>
</head>
<body>
<c:forEach var="test" begin="1" end="10" step="2">
	<b>${test}</b>&nbsp;
</c:forEach>
<br>
<c:forTokens var="alphabet" items="a,b,c,d,e,f,g,h,i,j,k,l,m,n" delims=",">
	<b>${alphabet}</b>&nbsp;
</c:forTokens>
<br>
<c:set var="data" value="홍길동,김길동,고길동"/>
<c:forTokens var="varData" items="${data}" delims=",">
	<b>${varData}</b>&nbsp;
</c:forTokens>
</body>
</html>

- forEach 태그로 1 ~ 10까지 2씩 증가된 값을 출력하고 있다, 1 3 5 7 9 가 출력됨

- forTokens 태그로 itmes 의 문자들을 , 로 구분해서 파싱해서 alphabet 변수에 저장 후 하나씩 출력

+ forTokens 의 itmes 에 바로 파싱할 문자열을 넣을 수도 있다

- set 태그로 data 변수를 만들어서 "홍길동,김길동,고길동" 값을 할당하고, forTokens itmes 로 data 를 써서 그 문자열을 구분기호 , 로 파싱해서 varData 에 저장 후 하나씩 출력

+ 변수 data 는 set 태그로 정의한 변수이므로 items 로 들어갈 수 있다.

 

+ &nbsp : 수평간격을 벌려줌

+ <b> : bold


기타 코어 라이브러리 태그

- import 태그 : 다른 문서를 불러올 수 있다.


JSTL 라이브러리 : 국제화 라이브러리

- 숫자나 날짜 출력에 사용하는 라이브러리

- SimpleDateFormat 이 사용불가능할때 사용

- formatDate 태그 : 자바의 SimpldDateFormat 과 비슷한 역할

- formatDate 태그를 가장 많이 사용, 원하는 포맷으로 날짜 포맷팅

- formatNumber 태그 : 자바의 DecimalFormat 과 비슷한 역할

- timeZone 태그 : 특정 타임존(시간대)을 지정

 

JSTL 예제 8

국제화 라이브러리

formatDate 태그 / 날짜 포맷 지정

- JSTL/fmt/ex6/use_DateFormat.jsp

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

<html>
<head><title>numberFormat 태그 사용</title></head>
<body>

<c:set var="now" value="<%= new java.util.Date() %>" />
${now } <br>
<c:out value="${now }"/> <br><br>

<fmt:formatDate value="${now}" type="date" dateStyle="full" /> <br>
<fmt:formatDate value="${now}" type="date" dateStyle="short" /> <br>
<fmt:formatDate value="${now}" type="time" /> <br>
<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" /> <br>
<fmt:formatDate value="${now}" pattern="z a h:mm" /> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a hh:mm:ss EEE요일"/> <!-- 12시간제 --> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a HH:mm:ss EEE요일"/> <!-- 24시간제 -->

</body>
</html>

- 코어 라이브러리와 국제화 라이브러리를 불러오고 있다.

 

국제화 라이브러리 불러오기 (use_DateFormat.jsp 부분)

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

 

- prefix 는 "fmt" 이다

 

- now 변수에 Date 객체 생성해서 저장, now 변수는 현재시간을 가지고 있다.

 

now 변수를 그냥 출력한다면?

- EL 또는 out 객체로 출력

${now } <br>

- 위는 EL 출력, 아래는 out 객체로 출력

<c:out value="${now }"/> <br>

- 영미권 날짜 형식으로 나온다

 

- Model 2 에서는 SimpleDateFormat 과 EL 을 동시 사용 불가

- JSTL 국제화 라이브러리의 DateFormat 태그로 원하는 포맷으로 바꿔야한다

 

formatDate 태그 속성

- 앞에 fmt 란 prefix 를 쓰고 있다, value 속성값이 날짜 시간이 들은 변수가 된다.

- type 속성값으로 "date" 는 연월일(날짜) "time" 은 시분초(시간) "both"는 날짜와 시간이 모두 출력됨

- dateStyle 속성값으로 "full" 이면 복잡, "short" 이면 간략하게 나옴

 

dateStyle = "full" 일때 :날짜가 자세히 출력

dateStyle = "short" 일때 : 날짜가 간략히 출력

type = "time" 일때

- 시간만 출력됨

type = "both" 이고, dateStyle = "full" 이고, timeStyle = "full" 일때

- 날짜, 시간 둘 다 나오고, 둘 다 자세하게 나옴

 

formatDate 태그 패턴 지정

<fmt:formatDate value="${now}" pattern="z a h:mm" /> <br>

- pattern 속성으로 패턴 지정

- z :타임존, a : 오전/오후, h : 시간, mm : 분

- hh 는 12시간제 시간, HH 는 24시간제 시간

<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a hh:mm:ss EEE요일"/> <!-- 12시간제 --> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a HH:mm:ss EEE요일"/> <!-- 24시간제 -->


JSTL 예제 9

국제화 라이브러리

timeZone 태그 / 타임존 지정

- JSTL/fmt/ex6/use_timezone_tag.jsp

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

<html>
<head><title>timeZone 태그 사용</title></head>
<body>

<c:set var="now" value="<%= new java.util.Date() %>" />

<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" />

<br>
<fmt:timeZone value="Hongkong">
<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" />
</fmt:timeZone>

</body>
</html>

- Date 객체를 가지고 있는 now 변수, 현재 시간 가지고 있다

- formatDate로 날짜, 시간 둘 다 출력하고 둘 다 자세히 출력함

 

- timeZone 태그의 value 로 특정 시간대(타임존)를 지정 가능, 여기선 "Hongkong" 으로 지정

- timeZone 을 홍콩으로 설정하고 날짜를 출력하면 홍콩 타임존의 날짜와 시간이 출력됨

 

- 타임존을 특정 지역으로 설정 후, 날짜 출력하면 특정 타임존의 날짜와 시간 출력 가능

- 위는 한국 시간, 아래는 홍콩 시간


formatNumber 태그

- Java 에서 소수점 자리를 제어할때 사용했던 DecimalFormat 클래스와 유사

- 형식 :

- 여러 속성들이 있다

 

JSTL 예제 10

국제화 라이브러리

formatNumber 태그 / 숫자 포맷 지정

- JSTL/fmt/ex6/use_number_tag.jsp

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

<html>
<head><title>formatNumber 태그 사용</title></head>
<body>

<c:set var="price" value="10000" />
<fmt:formatNumber value="${price}" type="number" var="numberType" />
숫자: ${numberType} <br>

통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="$" /><br>
통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="\\" /><br>

퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="true" /> <br>
퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="false" /> <br>

패턴: <fmt:formatNumber value="${price}" pattern="00000000.00"/>

</body>
</html>

- set 태그로 price 변수를 정의하고 10000 으로 초기화

- formatNumber 태그로 이 10000 을 어떻게 출력할지 지정

- type 이 "number" 면 숫자 단위로 출력하겠다는 의미

- var 속성값은 변수명

 

숫자로 출력

<fmt:formatNumber value="${price}" type="number" var="numberType" />
숫자: ${numberType} <br>

- price 에 저장된 값을 숫자단위로 받아서 numberType 이라는 변수에 저장

- 이 numbeType 변수를 출력하면 일반적인 숫자가 출력됨

 

통화 단위로 출력

통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="$" /><br>
통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="\\" /><br>

- currencySymbol 속성으로 통화 단위 지정

+ 원(won) 출력하려면 "\\" 으로 역슬래시를 두번

 

퍼센트 % 출력

퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="true" /> <br>
퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="false" /> <br>

- true 설정시 3자리씩 끊어서 출력, false 설정시 끊어져 있지 않음

 

숫자 패턴 지정

패턴: <fmt:formatNumber value="${price}" pattern="00000000.00"/>

- pattern 속성에 포맷 작성

- 0 으로 패턴 지정 : 자리값이 맞지 않으면 0 을 채워서 출력


JSP 라이브러리 : 함수 라이브러리

- 쉽게 말하면 함수이다

- Java 의 String 클래스의 메소드와 유사한 메소드들이 많다

 

JSTL 예제 11

함수 라이브러리

여러가지 태그들

- JSTL/fn/use_function.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<html>
<head><title>함수 사용</title></head>
<body>
<c:set var="str1" value="Functions <태그>를 사용합니다. " />
<c:set var="str2" value="사용" />
<c:set var="tokens" value="1,2,3,4,5,6,7,8,9,10" />

length(str1) = ${fn:length(str1)} <br>
toUpperCase(str1) = "${fn:toUpperCase(str1)}" <br>
toLowerCase(str1) = "${fn:toLowerCase(str1)}" <br>
substring(str1, 3, 6) = "${fn:substring(str1, 3, 6)}" <br>
substringAfter(str1, str2) = "${fn:substringAfter(str1, str2)}" <br>
substringBefore(str1, str2) = "${fn:substringBefore(str1, str2)}" <br>
trim(str1) = "${fn:trim(str1)}" <br>
replace(str1, src, dest) = "${fn:replace(str1, " ", "-")}" <br>
indexOf(str1, str2) = "${fn:indexOf(str1, str2)}" <br>
startsWith(str1, str2) = "${fn:startsWith(str1, 'Fun')}" <br>
endsWith(str1, str2) = "${fn:endsWith(str1, "합니다.")}" <br>
contains(str1, str2) = "${fn:contains(str1, str2)}" <br>
containsIgnoreCase(str1, str2) = "${fn:containsIgnoreCase(str1, str2)}" <br>

<c:set var="array" value="${fn:split(tokens, ',')}" />

join(array, "-") = "${fn:join(array, "-")}" <br>
escapeXml(str1) = "${fn:escapeXml(str1)}" <br>

</body>
</html>

- 코어 라이브러리와 함수 라이브러리를 불러오고 있다

- set 태그로 str1,str2,str3 을 정의하며 각각 문자열로 초기화 한다

 

함수 라이브러리 불러오기 (use_function.jsp 부분)

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

- prefix 가 "fn" 이다

 

함수 사용 형식 (use_function.jsp 부분)

length(str1) = ${fn:length(str1)} <br>

- 함수 라이브러리를 쓸 때는 EL 안 (${}) 에서 쓴다

- prefix 인 fn 을 쓰고 : 다음 함수명을 써서 사용한다

 

함수 종류

- legnth(문자열) : 문자열의 길이 구해줌

- toUpperCase(문자열) : 문자열을 대문자로 변환

- toLowerCase(문자열) : 문자열을 소문자로 변환

- subString(문자열,시작,끝) :문자열 추출

- 인덱스 3 ~ 5까지 str1 문자열을 추출

- substringAfter(문자열 str1, 문자열 str2) : str1의 문자를 str2문자 다음의 문자를 추출

- str1에서 str2 인 '사용' 이라는 문자 다음의 것을 추출

- substringBefor(문자열 str1, 문자열 str2) : str1의 문자를 str문자 이전의 문자를 추출

- trim(문자열) : 공백을 없애줌

- replace(str1, " ", "-") : str1 의 공백을 "-" 으로 치환

 

- 전체 출력 캡처


JSTL 라이브러리 : 데이터베이스 라이브러리

- DB 연동이 되어야한다

- 실습 위해 테이블도 만들어야함

- INSERT, UPDATE, DELETE 는 update 태그, SELECT 는 query 태그 사용

 

JSTL 예제 12

실습 준비

- totoro 계정을 사용할 것이므로 오라클 totoro 계정을 연결해야한다

- 테이블명이 test 로 되어있다, 컬럼은 2개

create table test(
	num number,
	name varchar2(10),
	primary key(num) );

- totoro 계정으로 들어가서 테이블을 생성하자

- WebContent/sql/myoracle.jsp 에서 연결 후 테이블 생성

- 생성 후 확인

- test 테이블이 생성되었다

- num 컬럼, name 컬럼이 있고, num 컬럼이 기본키이다

 

JSTL 예제 12

데이터베이스 라이브러리

여러가지 태그들

- JSTL/sql/ex7/jstl_sql_ex.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
<html>
<head>
<title>JSTL sql 라이브러리 사용 예제</title>
</head>
<body>

<sql:setDataSource var="conn" driver="oracle.jdbc.driver.OracleDriver" 
				url="jdbc:oracle:thin:@localhost:1521:xe"
				user="totoro" 
				password="totoro123"/>

<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (1, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (2, '조준동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (3, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (4, '홍길순')
</sql:update>

<sql:query var="rs" dataSource="${conn}">
	SELECT * FROM test WHERE name=?
	<sql:param>홍길동</sql:param>
</sql:query>

<c:forEach var="data" items="${rs.rows}">
	<c:out value="${data['num']}"/>
	<c:out value="${data['name']}"/>
	<br>
</c:forEach>

</body>
</html>

- 연결하고, insert 수행하고, select 수행하고 있다

- INSERT, UPDATE, DELETE 는 update 태그, SELECT 는 query 태그 사용

 

데이터베이스 라이브러리 부르기

<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>

- prefix 는 "sql"

 

DB 접속 설정

<sql:setDataSource var="conn" driver="oracle.jdbc.driver.OracleDriver" 
				url="jdbc:oracle:thin:@localhost:1521:xe"
				user="totoro" 
				password="totoro123"/>

- user 속성과 password 를 실제 오라클 계정과 유저네임과 비밀번호로 설정

- 정보가 모두 일치하면 실행 시 하나의 Connection 객체가 생성된다

 

INSERT 수행

<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (1, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (2, '조준동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (3, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (4, '홍길순')
</sql:update>

- update 태그로 INSERT 수행

- dateSource 속성에 아까 만든 Connection 객체 conn 을 넣는다

- 삽입 확인

 

SELECT 수행

<sql:query var="rs" dataSource="${conn}">
	SELECT * FROM test WHERE name=?
	<sql:param>홍길동</sql:param>
</sql:query>

<c:forEach var="data" items="${rs.rows}">
	<c:out value="${data['num']}"/>
	<c:out value="${data['name']}"/>
	<br>
</c:forEach>

- ? 자리에 값을 설정할때 param 태그 사용,

- 즉 name 이 "홍길동" 인 데이터를 검색

- rs 가 검색결과를 받는다, Java의 ResultSet 과 비슷

- forEach 문의 itmes 에 rs.rows 로 한 행씩 가져와서 한 행의 'num' 컬럼의 값, 'name' 컬럼의 값을 출력하고 있다

 

- 1번만 실행해야한다, 결과 캡처

 

 


- Java Servlet, EL, JSTL 끝났다

- 이제 Model 2 로 완성된 프로그램 만들 것

- Controller 클래스 1개 Java Servlet 클래스로 만들기

- Service 클래스 여러개 만들기, 상속을 통해 통일성 있게 구현

- DTO, DAO 는 기존 Model 1 의 DAO, DTO 와 같다


 

JSP Model 2 회원관리 프로그램 만들기

 

회원관리 프로그램 : 주요 기능 소개

1. Connection Pool

2. request, session 객체 공유 설정

- 로그인 성공시 session 공유 시작, 로그아웃시 session 강제로 삭제

- 출력할 것들을 request 객체로 공유 설정

3. Controller 클래스 : Java Servlet

4. Model = Service + DAO

- Service, DTO, DAO 클래스

5. View (화면 인터페이스)

- 공유된 값을 EL , JSTL 사용해서 출력

 

회원관리 프로그램 : 프로그램 구조 설명, 구조도

- 이렇게 구조를 만들기 위해

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

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

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

- service 패키지 안에 Service 클래스들

 

<환경 설정>

- WebContent 에 폼, 유효성 검사 되어있는 파일이 있는 폴더 member 넣기

- META-INF 에 context.xml

- 자료실 위해서 cos.jar, JSTL 쓰려면 jstl, 오라클 쓰려면 ojdbc6.jar

<Controller 클래스>

- Controller 클래스는 Java Servlet 클래스로 만듬

- Controller 클래스 WebServlet 어노테이션에 패턴 지정, .do 하면 무조건 여기로 찾아옴

<Service 클래스>

- Service 클래스의 부모 인터페이스는 Action.java, 추상메소드 execute() 만을 가짐

- 그 인터페이스를 상속받은 많은 구현 클래스들을 만듬

- 어떤 방식으로 포워딩할지 결정하는 Service 클래스 : ActionForward.java

 

회원관리 프로그램 : 프로젝트 생성

- model2member 라는 이름으로 새로운 프로젝트 만들기

- 초기에 보여줄 파일인 index.jsp 를 WebContent 폴더 안에 생성

 

회원관리 프로그램 : HTML, Javascript 파일 가져오기

- 폼과 유효성 검사가 되어있는 파일을 가져오기

- 이 기본 파일들을 바탕으로 프로그램 구현

 

회원관리 프로그램 : 몇가지 환경 구축

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

- 자료실 위해서 cos.jar, JSTL 쓰려면 jstl-1.2.jar, 오라클 쓰려면 ojdbc6.jar 라이브러리

 

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

- 커넥션 풀의 환경설정 파일이다

- 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)          
-->

- 500 개의 커넥션이 미리 연결되어있다

 

회원관리 프로그램 : 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();
 	}
%>

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

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

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

 

회원관리 프로그램 : member2 테이블 생성

- totoro 오라클 계정에 member2 테이블을 생성

- Model 1 시간에 만들었던 회원 관리 프로그램과 같은 테이블명만 다르다

create table member2(
id varchar2(20) primary key,
passwd varchar2(20) not null,
name varchar2(20) not null,
jumin1 varchar2(6) not null,
jumin2 varchar2(7) not null,
mailid varchar2(30), 
domain varchar2(30), 
tel1 varchar2(5),
tel2 varchar2(5),
tel3 varchar2(5),
phone1 varchar2(5),
phone2 varchar2(5),
phone3 varchar2(5),
post varchar2(10),
address varchar2(200),
gender varchar2(20),
hobby varchar2(50),
intro varchar2(2000),
register timestamp );

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

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

- member2 테이블을 생성

- member2 테이블 생성 확인

 

회원관리 프로그램 : DAO 와 DTO 클래스 만들기

- Model 2 에서는 DAO, DTO 의 패키지가 달라진다

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

 

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

 

회원관리 프로그램 : DTO 클래스 작성

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

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

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

+ java.sql 의 Timestamp import

- 이후 getter / setter 메소드 추가

 

- DTO 클래스 완성 코드

- MemberDTO.java

// DTO (Date Transfer Object)
package model;

import java.sql.Timestamp;

public class MemberDTO {
	private String id;
	private String passwd;
	private String name;
	private String jumin1;
	private String jumin2;
	private String mailid; 
	private String domain;
	private String tel1;
	private String tel2;
	private String tel3;
	private String phone1;
	private String phone2;
	private String phone3;
	private String post;
	private String address;
	private String gender;
	private String hobby;
	private String intro;
	private Timestamp register;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getJumin1() {
		return jumin1;
	}
	public void setJumin1(String jumin1) {
		this.jumin1 = jumin1;
	}
	public String getJumin2() {
		return jumin2;
	}
	public void setJumin2(String jumin2) {
		this.jumin2 = jumin2;
	}
	public String getMailid() {
		return mailid;
	}
	public void setMailid(String mailid) {
		this.mailid = mailid;
	}
	public String getDomain() {
		return domain;
	}
	public void setDomain(String domain) {
		this.domain = domain;
	}
	public String getTel1() {
		return tel1;
	}
	public void setTel1(String tel1) {
		this.tel1 = tel1;
	}
	public String getTel2() {
		return tel2;
	}
	public void setTel2(String tel2) {
		this.tel2 = tel2;
	}
	public String getTel3() {
		return tel3;
	}
	public void setTel3(String tel3) {
		this.tel3 = tel3;
	}
	public String getPhone1() {
		return phone1;
	}
	public void setPhone1(String phone1) {
		this.phone1 = phone1;
	}
	public String getPhone2() {
		return phone2;
	}
	public void setPhone2(String phone2) {
		this.phone2 = phone2;
	}
	public String getPhone3() {
		return phone3;
	}
	public void setPhone3(String phone3) {
		this.phone3 = phone3;
	}
	public String getPost() {
		return post;
	}
	public void setPost(String post) {
		this.post = post;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getHobby() {
		return hobby;
	}
	public void setHobby(String hobby) {
		this.hobby = hobby;
	}
	public String getIntro() {
		return intro;
	}
	public void setIntro(String intro) {
		this.intro = intro;
	}
	public Timestamp getRegister() {
		return register;
	}
	public void setRegister(Timestamp register) {
		this.register = register;
	}
	
}

 

회원관리 프로그램 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2.  정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드

 

1. 싱글톤

- 자기자신의 클래스로 객체 생성을 한번만 하고 static 을 붙여 공유 , private 설정

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 이 3줄을 복사해서 넣는다

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

+ Context 와 DataSource 둘 다 Interface 이다

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

+ 리턴자료형을 private 으로 해서 DAO 클래스 내에서만 호출가능하도록 함

 

- 현재까지 DAO 클래스 BoardDBBean.java 코드

// DAO (Date Access Object)
package dao;

import java.sql.Connection;

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

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행 하는 것.
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO 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. 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드

- 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드가 오면 된다

- 이 DAO 메소드를 "Service 클래스"에서 호출해서 SQL문 실행할 것

- 그래서 값이 Service -> DAO 로 넘어간다

 

회원관리 프로그램 : Controller 클래스

- Java Servlet 클래스로 만든다

- 모든 요청의 진입점

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

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

 

- MemberController.java (수정 전)

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 MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController 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); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

	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" 는 앞쪽의 파일명은 어떤 이름이어도 상관없고, 확장자가 do 인 모든 요청을 받는다는 의미

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

 

ex) 네이버에서 확장자는 .naver 사용

 

+ WebContent/memer/memberform.jsp 에서 form 의 action 값을 확인해보자

- 앞의 이름과 상관없이 확장자가 do 이므로 무조건 MemberController 클래스로 찾아감

 

+ WebContent/memer/loginform.jsp 에서 form 의 action 값을 확인해보자

- 앞의 이름과 상관없이 확장자가 do 이므로 무조건 MemberController 클래스로 찾아감

 

- Model 2에서 모든 클라이언트의 요청은 하나의 Controller 클래스로 가야한다

- WebServlet 어노테이션을 "*.do" 로 설정시 클라이언트(폼)에서 do 확장자로 요청하면 무조건 그 요청을 받음

- 이 패턴 *.do 에 맞으면 그곳으로 찾아감

 

doProcess() 안의 내용 (MemberController.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);
		System.out.println("contextPath : " + contextPath);
		System.out.println("command : " + command);
	}

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

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

- getRequestURI() : 현재 프로젝트명과 요청 URI 를 구해줌 ex) /model12member/Login.do

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

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

- 추출시 요청 URI 만 남게되고, 이 요청 URI 가 command 변수에 저장됨

 

- loginform.jsp 에서 Controller 클래스로 요청을 해보자

- loginform.jsp 실행하기

- 아무 값이나 로그인 폼에 입력하고 '로그인' 누르기

- 그럼 "/login.do" 로 요청, 확장자가 do 이므로 무조건 Controlelr 클래스로 찾아간다

- 값 전송방식이 post 이므로 doPost() 가 실행됨, doProcess() 호출됨

 

- 콘솔창 확인

- post  : doPost() 가 호출되었다는 의미

- requestURI 는 /model2member/Login.do 가 출력됨

- contextPath 는 /model2memer 가 출력됨

- command 는 /Login.do 가 출력됨

 

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

		String command = requestURI.substring(contextPath.length());

- contextPath.length() 는 13 이고, requestURI.substring(13) 은 requestURI 의 인덱스 13부터 쭉 출력하라는 의미

- requestURI는 /model2member/Login.do 이므로 13부터는 /Login.do 이고 이게 command 변수에 저장됨

- command 변수에는 최종적으로 요청 이름값이 저장됨 , 즉 action 값으로 지정된 "/Login.do" 가 저장됨

- 이제 어디서 요청이 왔는지 확인이 가능하다

ex) command 가 "/Login.do" 이면 로그인 페이지 와서 왔음을 알 수 있음

 

- 조건문과 command.equals() 를 사용해서 어디서 온 요청이냐에 따라 전달될 Service 클래스를 지정함

ex) command.equals("/Login.do")

 

+ Controller 클래스는 또한 포워딩 2가지 방법 중 어떤 방식으로 포워딩 될지도 처리함

- Dispatcher 방식으로 포워딩할지, Ridirect 방식으로 포워딩할지 Controller 클래스에서 정한다

 

 

 

 

Model 1 설계 방식

- 클라이언트가 브라우저를 통해 요청

- 서버측 JSP 에서 요청을 받아서 변수에 저장하고, JavaBean (DTO) 객체 생성해서 값을 저장

- DAO로 DB 연동을 처리

- 많은 부분을 JSP 코드로 처리


Model 1 설계 특징

- JSP 만 이용하여 개발하는 경우

- JSP + Java Bean (DTO)을 이용하여 개발하는 경우

- Model2의 Controller 개념이 모호함

 

Model 1 장점

- 개발속도가 빠름

- 개발자의 기술적인 숙련도가 낮아도 배우기 쉬워서 빠르게 적용이 가능함

 

Model 1 단점
- JSP 페이지에서 프레젠테이션 로직과 비즈니스 로직이 혼재되어서 복잡함

- 로직의 혼재(한 파일안에 Java, JSP, HTML 혼용)로 인해서 개발자와 디자이너의 작업 분리가 어려움

- JSP 코드의 복잡도로 인해서 수정, 유지보수가 어려워짐

- 단점들로 인해 Model 1 방식을 잘 사용하지 않음


Model 2 설계 방식 1

- 클라이언트가 브라우저를 통해 요청

- 3가지 영역으로 기능을 분리시켜 개발

- 모든 요청의 진입점 역할을 하는 Controller Class, 중간의 흐름을 제어함, 요청을 어떤 클래스로 넘겨줄지를 처리

- JavaBean 사이에, 즉 2번에 Service Class 를 만들것

+ 위 사진은 대략적인 그림임

 

+ Java Servlet

- Java Servlet 으로 Controller Class 를 만든다

- Java Servlet : 자바로 만들어진 웹 프로그램, HTML, JS 코드도 포함됨, 웹 상에서 사용가능한 서버 기능도 가지고 있다

+ 자바시간에 만든 자바는 Java Application, 내 컴, 자바 가상먼신 에서만 실행된다

 

Model 2 설계 특징

- GUI 개발모델인 MVC를 웹 애플리케이션에 적용하여 구현한 방식

- Application의 역할을 Model – View – Controller로 분리시켜서 개발하는 방식

 

Model 2 장점

- 비즈니스 로직(개발)과 프리젠테이션(디자인)의 분리로 인해 애플리케이션이 명료해지며 유지보수와 확장이 용이함

- 디자이너와 개발자의 작업을 분리해 줌

 

Model 2 단점

- 개발 초기에 아키텍처 디자인을 위한 시간의 소요로 개발 기간이 늘어남

- MVC 구조에 대한 개발자들의 이해가 필요


MVC 패턴

- MVC는 Model / View / Controller 의 약자로 애플리케이션을 세 역할로 나누어서 개발하는 개발 방법론

- View 페이지는 디자이너가 처리, Model 과 Controller Class 는 개발자가 처리

- Spring 등의 프레임워크들도 모두 MVC 패턴으로 되어있다

 

Model

- 애플리케이션의 데이터 처리를 담당함

- Service 클래스 + DAO 클래스로 구현함

 

View

- 화면에 출력되는 페이지

- 사용자 인터페이스를 처리함

- JSP 를 이용해서 구현함 : EL(Expression Language, 표현언어) + JSTL(JSP Standard Tag Library)

 

Controller

- 클라이언트의 요청을 받아 Model과 View사이에서 흐름을 제어한다.

- Java Servlet으로 구현

 

+ Model 2 보다 빠르게 개발하기 위한 프레임 워크 : 쟝고, Spring

+ Model, Controller 는 Java 로 구현


Model 2 MVC 패턴 아키텍처

- 클라이언트가 서버측에 요청함

 

Controller Class

- 모든 요청의 진입점 역할을하는 Controller 클래스가 요청을 받음

- Controller 클래스는 중간의 흐름을 제어

- Java Servlet 으로 Controller Class 를 만든다

- 어떤 Service Class 로 요청을 넘길건지 결정, Controller 에서 Service 로 넘어간다

- 또한 어떤 VIew 페이지로 포워딩 할 것인지 결정

 

Service Class

- 자바로 만들어짐

- Service 클래스를 만들때 상속 사용

- Service 클래스는 여러개를 만드는데 공통적인 내용을 부모 인터페이스로 만들고 나머지는 공통적인 내용을 부모 인터페이스로부터 상속받아서 서비스 클래스를 여러개 만듬

- Service Class에서 DAO Class로 넘어간다

 

DAO

- Model 1 의 코드와 같다

- DB 처리는 모두 DAO 에서 한다

- Service Class + DAO Class = Model = 실제 요청을 처리해줌

 

DTO

- Model 1 의 코드와 같다

- Controller -> Service -> DAO 에서 이동할때 값의 전달은 DTO 객체를 사용함

- 반대로 DAO -> Service -> Controller 로 값을 DTO 객체로 돌려줄 수도 있다

 

공유 설정

- DB에서 검색한 데이터들 DAO에서 Service 로 리턴하고, Service Class 에서 그 데이터를 request  영역 객체로 공유 설정

- 공유한 값들은 Controller 클래스, View 페이지에서 사용(출력)한다

- 이때 표현언어(EL), JSTL 로 출력하면 다운캐스팅을 할 필요 없는 장점이 있다

+ 표현식 태그 대신 표현언어와 JSTL 사용

 

값의 흐름

- 값이 넘어가는 순서 : Controller -> Service -> DAO

- 값을 리턴할때 ( DAO 에서 검색결과를 돌려줄때 ) : DAO -> Service -> Controller -> View에서 출력

 

Model 2 개발을 위해 학습 할 것

- Java Servlet 으로 Controller 클래스 만드므로 Java Servlet

- 결과 출력시 표현식 태그를 대신하는 표현 언어(EL)

- JSTL 도 같이 사용되므로 JSTL

+ EL, JSTL 은 Model 2 뿐 아니라 Spring, Spring Boot 에도 똑같이 사용됨


Java Servlet 클래스

- Java로 작성된 웹프로그램을 의미함

- Java Servlet 클래스에는 HTML, JavaScript 코드를 포함할 수 있다

- Java Servlet 클래스는 웹브라우저로 실행 결과를 출력할 수 있다

- JSP 파일이 내부적으로 Java Servlet 으로 변환됨, 이젠 직접 Java Servlet 을 작성하는 것

- Controller 클래스를 만들때 사용함

- 모든 요청의 진입점 역할

 

Java Servlet 클래스 특징

- 확장자가 java 이므로 src 폴더 안에 넣어야한다.

- 실행시 웹 브라우저에 결과가 바로 나타난다

 

+ 실습 준비

- 새 프로젝트 jsppro 생성

- 이 프로젝트 내에서 Java Servlet, EL, JSTL 실습할 것

- 클라우드의 servlet 폴더를 jsppro/WebContnet 로 복사

- .java 파일은 src 파일에 생성해야함, WebContent 안에선 정상작동하지 않음

- src 폴더에 직접 Java Servlet 파일을 만들어보자

- Perspective 가 Java EE 여야 Java Servlet Class 생성 가능

 

Java Servlet 클래스 생성 / 실습 준비

- Java 이므로 Class 명이 파일명이 된다

- Superclass : 부모 클래스가 된다, HttpServlet가 부모클래스로 자동으로 만들어짐

- Helloworld Class 는 HttpServlet Class 를 내부적으로 상속받음

- Constructors from superclass 체크 해제 : 부모 클래스로 생성자가 만들어지는데, 필요하지 않으므로 체크 해제

- Inherited abstract methods 체크시 : 상속받을 메소드인 부모클래스 HttpServlet 중에서 필요한 메소드만 체크 가능

- 그 메소드 중 doGet 메소드와 doPost 메소드가 필요하므로 체크, 체크시 오버라이딩 됨

- doGet 메소드 체크 시 : 클라이언트가 get방식 요청시 자동으로 실행

- doPost 메소드 체크 시 : 클라이언트가 post방식 요청시 자동으로 실행

- Finish 누르면 src 폴더 내에 java 파일이 완성됨


- 예제 ex111 폴더

 

- HelloWorld.java

package send;

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 Helloworld
 */
@WebServlet(description = "처음 작성하는 자바 서블릿", urlPatterns = { "/Helloworld" })
public class Helloworld extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

<상속>

- Servlet Class 들은 기본적으로 HttpServlet Class 를 상속함

- 이 클래스는 HttpServlet Class의 doGet, doPost 메소드를 오버라이딩 해서 만들어짐

- extends 로 HttpServlet 클래스를 상속받아서 사용함, import javax.servlet.http.HttpServlet; 를 import 해야함

- import 되어있는 HttpServlet, WebServlet, HttpServletRequest Class 들은 Java EE 클래스로서 Apache Tomcat 에 저장되어있다

- Apache Tomcat 의 servlet-api.jar 라이브러리에 있는 클래스들임

- Java Servlet 클래스 생성 및 실행을 위해서는 반드시 아파치 톰캣이 있어야함

<메소드>

- doGet 메소드 : 클라이언트가 Get 방식 요청시 오버라이딩 메소드가 자동으로 실행됨

- doPost 메소드 : 클라이언트가 Post 방식 요청시 오버라이딩 메소드가 자동으로 실행됨

- 이 클래스 HelloWorld 그냥 실행시, Get 방식이 디폴트이므로, doGet 메소드만 자동 실행됨, doGet 메소드 안의 코드가 브라우저에 출력됨

 

@WebServlet

- 웹 서블릿 어노테이션이라고 부름

- 웹 서블릿 어노테이션, 실행시 이것 덕분에 찾아올 수 있다

- import javax.servlet.annotation.WebServlet; 클래스를 import 해서 사용가능

 

urlPatterns

- 현재 클래스명과 동일하게 자동으로 만들어짐

- 반드시 클래스명과 일치할 필요는 없음, 나중엔 값을 바꿀것

 

description

- 아까 생성시 작성함

 

- HelloWorld.java 실행시

- 기본전송방식이 get 이므로 doGet 메소드 안의 내용이 실행되어서 출력되었다

 

doGet() 오버라이딩 메소드

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

<형식>

- 이 형식을 그대로 사용해야함

- 매개변수가 request 와 response 객체로 되어있고 throws 로 예외를 던짐

- Java Servlet 에는 JSP 와 달리 내장객체가 없다

- 객체 request 와 객체 response 만 받아서 사용 가능하고, out 등의 다른 내장 객체는 없으므로 만들어서 사용해야한다

- 이 HttpServletRequest, HttpServletResponse 클래스를 사용하고 있다, 찾아보면 메소드를 찾아볼 수 있음

<내용>

1. 매개변수로 받은 response 객체의 response.getWriter() 로 out 객체를 구해와서 출력스트림 객체를 만듬

2. 내용(Served at:)을 append 시키고, request.getContextPath() 로 현재 프로젝트명(jsppro)을 구해서 append 로 추가해서 출력시키고 있음

 

- doGet() 메소드를 수정해보자

- WebContent에 있던 HelloWorld.java 의 내용을 src의 HelloWorld.java 로 일부 복사

- HelloWorld.java (doGet() 메소드 수정)

package send;

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

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 Helloworld
 */
@WebServlet(description = "처음 작성하는 자바 서블릿", urlPatterns = { "/Helloworld" })
public class Helloworld extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		// 현재 문서의 한글값을 인코딩(utf-8)을 시켜준다.
        response.setContentType("text/html;charset=utf-8");
        
        PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
        
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("</body>");
        out.println("</html>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

+ 주의 : @WebServlet 때문에 WebContent 의 HelloWorld.java 가 실행되어있으면 src의 HellowWorld.java 가 실행 안됨

- 현재 클래스를 그냥 실행시 기본 전송방식이 get 방식이므로 오버라이딩 된 doGet() 메소드가 자동 실행됨

 

doGet() 형식

- 이 형식을 그대로 사용해야함

- 매개변수가 request 와 response 객체로 되어있고 throws 로 예외를 던짐

- Java Servlet 에는 JSP 와 달리 내장객체가 없다

- 객체 request 와 객체 response 만 받아서 사용 가능하고, out 등의 다른 내장 객체는 없으므로 만들어서 사용해야한다

- 객체 request 와 객체 response 는 HttpServletRequest, HttpServletResponse 클래스를 사용하고 있다, 찾아보면 메소드를 찾아볼 수 있음

 

+ JSP의 내장객체

- Java Servlet 에는 내장객체가 없다, 쓸 수 있는 것은 doGet 과 doPost 에서 지원되어 매개변수로 전달되는 request, response 만 사용 가능

- 나머지 객체들은 만들어서 사용해야함

- 브라우저에 출력하고자 하면, out 객체를 생성해야함

 

out 객체 생성 방법

        PrintWriter out = response.getWriter();

- response.getWriter() 로 출력스트림 객체를 만듬, 이름을 out 으로 함

- 결과를 돌려줄때는 PrintWriter 클래스로 돌려줌

+ PrintWriter 클래스는 java.io 패키지 안에 있다, import 되어있음

- 브라우저에 출력할때는 out 객체를 생성해야함, JSP 가 아니므로 표현식 태그 사용 불가

 

화면 출력

        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("</body>");
        out.println("</html>");

- out.print 나 out.println() 사용해서 출력

- 소스코드에 HTML 코드도 들어갈 수 있고 Javascript 코드도 들어갈 수 있다

+ 한글이 들어가므로 한글 인코딩을 시켜야함

 

한글 인코딩

- 한글이 깨지지 않게 하기 위해 여태껏 JSP 에서는 page 태그의 이 코드를 사용했음

- contentType 속성의 charset 으로 인코딩

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

- 이 역할을 Java Servlet Class 에서는 이 코드를 사용해서 인코딩

	// 현재 문서의 한글값을 인코딩(utf-8)을 시켜준다.
        response.setContentType("text/html;charset=utf-8");

- 이 코드가 있어야 현재 문서의 한글값이 깨지지 않고 utf-8 로 인코딩되어 출력됨

- 이 코드가 없으면 한글값이 깨짐

 

+ get 방식과 post 방식 두가지 중 한가지로 요청이 들어오므로 각각의 경우 실행될 내용을 doGet() 과 doPost() 메소드 안에 작성

 

만들어서 사용할 객체

- 출력을 위해 out 객체 (출력스트림 객체) 만들기 (검색어 : out 객체 생성 방법)

- 회원관리에서 사용하기 위해 session 객체 만들기

 

session 객체 생성 방법

        HttpSession session = request.getSession();	// 세션 객체 생성

- request 객체로 만든다

- HttpSession 클래스로 결과를 돌려준다

- 로그인 성공 후 공유 설정할때 session 객체를 주로 사용


- 예제 ex222 폴더

 

form 을 통해서 요청하기

- 클라이언트가 html 에서 폼을 통해서 get/post 방식으로 Java Servlet 클래스로 찾아간다

- WebContent/ex222 의 Method.html 파일에서 src 폴더의 Method.java Java Servlet Class 로 찾아갈 것

 

- src 폴더에 Method.java 를 만들자

 

실습 준비

- Method.java (수정 전)

- 웹 서블릿 어노테이션이 만들어져있고 이 이름값 "/Method" 로 찾아오는 것임

- 기본적으로 HttpServlet Class 를 상속함

 

- 이제 WebContent/ex222 안의 Method.java doGet(), doPost() 안의 내용을 복붙해서 src 안의 Method.java 로 복사

- Method.java (수정 후)

package send;

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

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 Method
 */
@WebServlet("/Method")
public class Method extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<h1>get 방식으로 처리됨</h1>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<h1>post 방식으로 처리됨</h1>");
	}

}

- 클라이언트에서 get 방식 요청시 오버라이딩 된 doGet() 메소드 자동 실행

- 클라이언트에서 get 방식 요청시 오버라이딩 된 doPost() 메소드 자동 실행

- 두 메소드의 내용에서 공통적으로 한글 인코딩, 출력스트림 객체 out 생성, 출력 을 하고 있다 

 

웹 서블렛 어노테이션

@WebServlet("/Method")

- 클라이언트가 폼에서 요청할 때 이 이름값 "/Method"을 가지고 찾아옴

- 이름값은 기본으로는 자기 클래스와 같은 이름, 이름 바꿔도 된다

@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })

- 또는 이런 형식도 있다

- 이름값은 urlPatterns 안의 값 "/QueryString" 이다

 

- 이제 폼에서 Servlet Class 로 요청해보자

- WebContent/ex222/Method.html

<html lang="ko">
<head>
	<meta charset="utf-8">
</head>
<body>
	<!-- action = "/프로젝트명/서블릿파일명"  
		 패키지명은 기술안함.
	-->
	<form method="get" action="/jsppro/Method">
		<input type="submit" value="get 방식으로 호출하기">
	</form>

	<form method="post" action="/jsppro/Method">
		<input type="submit" value="post 방식으로 호출하기">
	</form>
</body>
</html>

- 폼 태그가 2개이고, 첫번째 form 태그는 get 방식, 두번째 form 태그는 post 방식

+ 한 파일에 여러개의 form 태그 쓸 수 있다, 중첩은 되지 않는다

- 버튼 2개가 있고 첫번째 버튼 클릭시 해당 Servlet 클래스로 get 방식 요청, 두번째 버튼 클릭시 해당 Servlet 클래스로 post 방식 요청함

- 이때 찾아가는 방법은 action 에 적힌 어노테이션 이름값으로 찾아간다

- Model 1에서는 action 값에 jsp 파일을 입력했지만, 지금 Model 2에서는 WebServlet 어노테이션 이름값으로 찾아감

 

ex)

- 사용자가 'post 방식으로 호출하기' 버튼을 클릭시 두번째 폼이 post 방식으로 전송됨

- 이때 어노테이션이 "/Method" 인 곳으로 찾아감

- post 방식으로 요청했으므로 그 클래스 Method Class 에서 doPost() 메소드가 실행됨

 

WebServlet 어노테이션으로 찾아가기 (Method.html 부분)

	<form method="get" action="/jsppro/Method">
		<input type="submit" value="get 방식으로 호출하기">
	</form>
    
	<form method="post" action="/jsppro/Method">
		<input type="submit" value="post 방식으로 호출하기">
	</form>

- /jsppro 는 프로젝트명 으로 인식, 이름값만으로 잘 못찾아가는 경우가 있으므로 현재 프로젝트 명을 붙임

- /Method 가 이름값 으로 인식

- action 값 지정시 패키지명은 경로상에 포함되지 않음.

+ 나중엔 현재 프로젝트명인 /jsppro 를 직접 쓰지 않고 request 객체 제공 메소드 현재 프로젝트명을 구해주는request.getContextPath() 사용함

+ 이 파일은 확장자가 HTML 이라서 JSP 코드 못쓰므로 직접 /jsppro 썼다

 

WebServlet 어노테이션 이름값 변경

- 원래 이름값은 클래스명과 동일하지만 이름값을 임의로 변경 가능하다

- Method.java 의 WebServlet 어노테이션을 "/test" 로 변경

@WebServlet("/test")

- 그럼 form 태그 안의 action 도 "/jsppro/test" 로 바꿔야함

	<form method="get" action="/jsppro/test">
		<input type="submit" value="get 방식으로 호출하기">
	</form>

	<form method="post" action="/jsppro/test">
		<input type="submit" value="post 방식으로 호출하기">
	</form>

- 이름값만 맞으면 찾아간다

 

- Method.html 실행시 잘 처리된다

 

+ Java Servlet 클래스 여러개 만들때 WebServlet 어노테이션 이름값이 다른 값이어야함


- 예제 ex333 폴더

- QueryString.html : 회원가입 양식

- QueryString.java : Java Servlet 클래스 명

 

- ex333/QueryString.java 를 src 폴더의 send 패키지 안으로 복붙

 

- QueryString.html

<html lang="ko">
 <head>
  <meta charset="utf-8">
 </head>
 <body>

<form method="get" action="/jsppro/QueryString">
 아 이 디 : <input type="text" name="id"  size="20"> <br>
 비밀번호 : <input type="password" name="pw"  size="20"> <br>
 회원이름 : <input type="text" name="name"  size="20"> <br>
 회원구분 : <input type="radio" name="class"  value="일반회원"> 일반회원
           <input type="radio" name="class"  value="교수님"> 교수님<br>
 전화번호 : <select name="phone1">
                 <option value="010" selected> 010 </option>    
                 <option value="011"> 011 </option>    
                 <option value="016"> 016 </option> 
                 <option value="017"> 017 </option>    
                 <option value="018"> 018 </option>    
                 <option value="019"> 019 </option>
           </select>              
    -      <input type="text" name="phone2" size="4" maxlangth="4">
    -
			<input type="text" name="phone3" size="4" maxlangth="4"><br>
			<input type="submit" value="전송">
		</form>
	</body>
</html>

- action 으로 지정된 어노테이션으로 이동뿐 아니라 값까지 전달한다

- WebServlet 어노테이션 이름값이 "/QueryString" 인 곳 으로 찾아가고 값을 전달

- get 방식으로 전송하고 있다

- name 값은 사용자가 입력한 값을 전달하기 위한 변수역할을 한다

+ 받는 쪽에서는 request.getParamter() 로 값을 받음

- 라디오 값의 value 값이 한글값임

 

- 가입을 시켜보자

+ 서버 중지 후 실행해야함

+ '다시' 누르면 다시 폼으로 돌아감

- 한글 값이 넘어가고 있다, 한글 인코딩이 어떻게 되는지도 자세히 보자

- Model 1 때 처럼 get 방식으로 "한글 값" 전달시 아파치 톰캣에서 자동으로 utf-8 로 인코딩 시킴

 

- QueryString.java

- src 폴더 안에 있다

package send;

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

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 QueryString
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })
public class QueryString extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub

		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		id = request.getParameter("id");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(get방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		response.setContentType("text/html;charset=utf-8");

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		String pw = "";
		id = request.getParameter("id");
		id = request.getParameter("pw");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(post방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
		
		QueryDTO query = new QueryDTO();
		
	}

}

- form 에서 get 방식으로 요청했으므로 doGet() 메소드가 자동 호출됨

- form 에서 넘어온 값을 request.getParameter() 로 양식의 name 값을 사용해서 받는다

- 받은 값을 출력하고 있음

+ doGet(), doPost() 메소드들은 오버라이딩되었으므로 이름 및 형식은 바꿀 수 없다, 내용은 바꿀 수 있다

 

+

		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");

- 여기선 HTML 코드도 쓸 수 있고 JS 코드도 쓸 수 있음

 

현재 문서 한글 인코딩 (QueryString.java 부분)

		response.setContentType("text/html;charset=utf-8");

- 이 코드는 "한글 값"과는 상관없이 "현재 문서의 한글"을 인코딩 시키는 것임

 

- QueryString.html 에서 form 의 method 를 "post" 로 변경시 QueryString.java 의 doPost() 메소드가 실행됨

- post 방식으로 한글 값이 전달될 때는 아파치 톰캣이 자동으로 한글 인코딩 해주지 않는다!

- response.setContentType() 으로 직접 인코딩 해주면 된다

 

한글 값 인코딩

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");

- "한글 값" 인코딩은 request 객체 사용

- post 방식으로 한글값이 전송될때 인코딩 해야한다

 

- DTO 클래스 및 DAO 클래스도 다 만들어져 있다

- WebContent/ex333 폴더 안에 있는 QueryDTO.java, QueryDAO.java 를 src 의 send 패키지 안으로 넣기

- QueryDTO.java

package send;

public class QueryDTO {

	private String id;
	private String pw;
	private String name;
	private String vclass;
	private String phone;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getVclass() {
		return vclass;
	}
	public void setVclass(String vclass) {
		this.vclass = vclass;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	
}

- QueryDAO.java

package send;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class QueryDAO {

	private static QueryDAO instance = new QueryDAO();
	
	public static QueryDAO getInstance(){
		return instance;
	}
	
	public void insert(QueryDTO dto){
		Connection conn = null;
		PreparedStatement pstmt = null;
		String url = "jdbc:oracle:thin:@localhost:1521:xe";
		String sql="";
		
		try{
			Class.forName("oracle.jdbc.driver.OracleDriver");
			conn = DriverManager.getConnection(url,"totoro","totoro123");
			
			sql="insert into query values(?,?,?,?,?)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getId());
			pstmt.setString(2, dto.getPw());
			pstmt.setString(3, dto.getName());
			pstmt.setString(4, dto.getVclass());
			pstmt.setString(5, dto.getPhone());
			pstmt.executeUpdate();		
			
		}catch(Exception e){
			
		}finally{
			if(pstmt != null){
				try{
					pstmt.close();
				}catch(Exception e){					
				}
			}
			if(conn != null){
				try{
					conn.close();
				}catch(Exception e){					
				}
			}			
		}
		
	}
	
}

- 다 만들어져있으므로 테이블 생성 후 실행하면 연동처리 되고 실행됨

- DAO의 insert() 메소드를 사용해보자

 

- src/send 로 옮겨진 DTO, DAO 클래스를 QueryString.java 에서 사용해 보자

 

- QueryString.java 의 doPost() 안에서 DTO 객체 생성후 폼에서 넘어온 값을 저장 (메모리상에 저장)

- 저장 후, 그 DTO 객체를 DAO 클래스의 삽입 메소드 매개변수로 전달해서 insert 시도 (DB 테이블에 저장)

 

- QueryString.java (수정, doPost() 아래 코드 추가됨)

package send;

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

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 QueryString
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })
public class QueryString extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub

		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		id = request.getParameter("id");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(get방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		response.setContentType("text/html;charset=utf-8");

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		String pw = "";
		id = request.getParameter("id");
		pw = request.getParameter("pw");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(post방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
		
		QueryDTO query = new QueryDTO();
		query.setId(id);
		query.setPw(pw);
		query.setName(name);
		query.setVclass(vclass);
		query.setPhone(phone1 + "-" + phone2 + "-" + phone3); // 결합해서 저장
		
		QueryDAO dao = QueryDAO.getInstance();
		dao.insert(query);	// 회원 가입
	}

}

 

- 수정한 부분만

- 이제 폼에서 넘어온 값을 QueryString.java 에서 받고 그 값들을 DB 에 삽입 insert 함

 

- 폼 실행 전에 query 라는 테이블을 먼저 생성해야함

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

- myoracle.sql 에서 커넥션 프로파일 선택 후 테이블 create 문 작성

+ 이때 DAO 에 작성되어있는 insert 문을 참고하면

+ 컬럼이 5개여야함

- myoracle.sql

-- 모델2와 연동
select * from tab;
select * from query;

create table query(
	id varchar2(20),
	pw varchar2(20),
	name varchar2(20),
	vclass varchar2(20),
	phone varchar2(30)
);

- 테이블 생성하기

 

- 이제 최종 연동을 확인하기 위해 QueryString.html 을 실행해서 회원가입 시키기

- 이건 그냥 출력이고 가입이 DB 에 저장되었는지는 sql 파일에서 확인

- 지금은 Java Servlet 클래스 내에서 DTO, DAO 사용했다

- 나중엔 중간에 Service 클래스를 만들어서 처리한다

 


- 예제 ex555 폴더

 

- multiPara.html

<html lang="ko">
 <head>
  <meta charset="UTF-8">
</head>
<body>
<form method="post" action="/jsppro/multiPara">
<h2>액사서리</h2><br>
관심항목을 선택하세요...<br>
<hr>
 <input type="checkbox" name="item" value="신발"> 신발
 <input type="checkbox" name="item" value="가방"> 가방
 <input type="checkbox" name="item" value="벨트"> 벨트<br>
 <input type="checkbox" name="item" value="모자"> 모자
 <input type="checkbox" name="item" value="시계"> 시계
 <input type="checkbox" name="item" value="쥬얼리"> 쥬얼리<br>  
 <input type="submit" value="전송"> 
</form>
</body>
</html>

- 체크박스를 통해 하나의 name 값에 여러개의 value 값이 넘어감

- name 인 "item" 에 value 값들이 여러개 넘어간다, 이때 value 값들이 한글값이므로 받는쪽에서 한글값 인코딩해야함

+ 값을 받는 쪽에선 Model 1 때 처럼 request.getParamteterValues() 를 사용하고 배열에 저장하면 된다

- jsppro 프로젝트의 WebServlet 어노테이션 이름값이 multiPara 인 곳으로 찾아가서 값을 전달함

 

- WebContent/ex555 안의 multiPara.java 를 src 의 send 패키지 안으로 복사

 

- multiPara.html 파일을 실행

 

- multiPara.java

- scr 폴더 안에 있다

package send;

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

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 multiPara
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/multiPara" })
public class multiPara extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		String[] item;

		item = request.getParameterValues("item");
		out.println("선택된 항목이");
		
		try {
			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);
//				out.println(" : " + HanConv.toKor(item[i]));
			out.println("입니다.");
		} catch (Exception e) {
			out.println(" 없습니다.");
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		
		request.setCharacterEncoding("UTF-8");
		
		PrintWriter out = response.getWriter();
		String[] item;

		item = request.getParameterValues("item");
		out.println("선택된 항목이");
		
		try {
			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);
//				out.println(" : " + HanConv.toKor(item[i]));
			out.println("입니다.");
		} catch (Exception e) {
			out.println(" 없습니다.");
		}
	}

}

- post 방식으로 넘어왔으므로 doPost() 메소드 내용이 실행됨

 

현재 문서 한글 인코딩 vs 한글 값 인코딩

현재 문서 한글 인코딩 (multiPara.java 부분)

		response.setContentType("text/html;charset=UTF-8");

값(value)이 post 방식으로 넘어왔을때 인코딩 (multiPara.java 부분)

		request.setCharacterEncoding("UTF-8");

 

하나의 name 값에 여러개 value 가 넘어왔을때 처리 (multiPara.java 부분)

		String[] item;

		item = request.getParameterValues("item");

- request.getParamterValues() 로 값을 받아서 1차원 배열에 저장

			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);

- 출력하고 싶다면 반복문으로 출력

 

+ 서버 재구동 하는 방법 : java 파일에 아무곳에서 스페이스바, 한깐 띄우기, 다시 컴파일해야하므로 서버 재구동됨


- 예제 ex666 폴더

 

- src 폴더 하위에 login 패키지 생성

- WebContent/ex666 폴더의 LoginServlet.java 파일을 src의 login 패키지에 복사

 

- LoginServlet.java

package login;

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

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 javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
		
		
		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		if(id.equals("java")&& passwd.equals("java")){	// 회원 인증 성공
			
			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			
			// 1. Dispatcher 방식으로 포워딩
//			RequestDispatcher dispatcher = 
//					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
//			dispatcher.forward(request, response);
			
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");
		}
		else{	// 회원 인증 실패
			out.println("<script>");
			out.println("alert('아이디나 비밀번호가 일치하지 않습니다.')");
			out.println("history.back()");
			out.println("</script>");
		}
	}

}

- WebServlet 어노테이션 이름값이 "/login"

- 아이디가 "java" 고 비밀번호가 "java"면 로그인 성공, 로그인 성공시 session 으로 id 를 공유함

 

포워딩

			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);

- 아이디 비번 일치시 loginSuccess.jsp 로 포워딩 시킨다

 

- ex666/index.html 을 보자

- index.html

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<frameset cols="30%,*">
		<frame src="/jsppro/servlet/ex666/menu.jsp" name="leftFrame" />
		<frame src="/jsppro/servlet/ex666/login.jsp" name="rightFrame" />
	</frameset>
	<body>

	</body>
</html>

- frameset 태그로 프레임을 나눔, cols 속성은 세로방향으로 프레임을 나누겠다는 의미, 좌측 30% 우측은 나머지

- fram src 의 경로는 패키지명을 적고, WebContent 를 기준으로 파일까지 경로를 적기

- index.html 실행시

- 좌측엔 menu.jsp, 우측엔 login.jsp 가 나타남

 

- menu.jsp

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

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
<%
	String id = (String)session.getAttribute("id");
%>
<body>
<%
	if(id == null){
%>
   <a href="login.jsp" target="rightFrame" />로그인</a>
<% 
	}
	else{
%>
	<%=id %> 님 환영합니다.
<%
	}
%>

</body>
</html>

- 세션 id 값을 구해옴

- 로그인을 하지 않으면 session 값이 null 이므로 로그인 링크만 뜬다

- 로그인을 하면 session 값이 null 이 아니므로 ~님 환영합니다 가 나옴

 

- login.jsp

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

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	<form action="/jsppro/login" method="post">
		아이디 : <input type="text" name="id"/>
		비밀번호 : <input type="password" name="passwd"/><br>
		<input type="submit" value="로그인"/>
	</form>
</body>
</html>

- 아이디, 비번 입력양식이 있고 로그인이 가능한 페이지

- form 을 통해 WebServlet 어노테이션 이름값이 "/login" 인 곳으로 찾아가서 post 방식으로 값을 전달한다

- 어노테이션 이름값이 "/login"인 LoginServlet.java 에서 doPost()가 실행됨

 

- LoginServlet.java (중복)

package login;

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

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 javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
		
		
		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		if(id.equals("java")&& passwd.equals("java")){	// 회원 인증 성공
			
			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			
			// 1. Dispatcher 방식으로 포워딩
//			RequestDispatcher dispatcher = 
//					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
//			dispatcher.forward(request, response);
			
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");
		}
		else{	// 회원 인증 실패
			out.println("<script>");
			out.println("alert('아이디나 비밀번호가 일치하지 않습니다.')");
			out.println("history.back()");
			out.println("</script>");
		}
	}

}

- WebServlet 어노테이션으로 요청을 받음

- doPost() 메소드가 자동으로 실행됨

<doPost() 내용>

- 한글 값 인코딩, 현재 문서 한글 인코딩

- 아이디나 비번이 틀렸을때 브라우저에 메세지 출력위한 out 객체(출력스트림 객체) 생성

- 전달된 값 받기

 

전달된 값 받기 (LoginServlet.java 부분)

		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");

- form 에서 넘어온 데이터를 request.getParamter() 로 받고 있다

 

- 여기선 DB연동 대신 아이디, 비밀번호 값이 "java" 인 경우 로그인 성공으로 간주

- 로그인(회원 인증) 성공시 세션으로 공유 설정

 

세션 객체 생성 & 세션 공유 설정 (LoginServlet.java 부분)

			HttpSession session = request.getSession();
			session.setAttribute("id", id);

- session 이 내장 객체가 아니므로 session 객체를 만들어서 사용해야함

- 로그인 성공시 session 객체를 만들어서 공유해야함

- session 으로 setAttribute() 로 공유설정함

- 여기서부터 세션 영역 시작, 로그인 성공 부터 세션 영역 시작

- session value 값이 id 값이다, 즉 "java"

 

포워딩 (LoginServlet.java 부분)

			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);

- 포워딩하는 2가지 방법 중 Dispatcher 를 통한 포워딩 방법이다

- request.getRequestDispatcher() 로 경로 설정 후 포워딩할 파일을 불러오고 dispatcher.forward() 사용

- 즉, loginSuccess.jsp 로 포워딩하겠다는 의미, 이동하겠다는 의미

 

- loginSuccess.jsp

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

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	<script>
	top.leftFrame.location.href="/jsppro/servlet/ex666/menu.jsp";
	</script>
	</head>
	<body>
		로그인 성공
	</body>
</html>

- index.html 의 좌측 프레임 name 값이 "LeftFrame" 이었다

- 즉 loginSuccess.jsp 의 코드는 좌측 프레임 쪽(menu.jsp)으로 이동하겠다는 의미

 

- menu.jsp 로 이동

- menu.jsp 에서 이젠 session 값을 받아올 수 있으므로 더이상 null 이 아님

- java님 환영합니다가 나타나게 된다

 

- 이제 index.html 실행해보자

- 우측 화면에서 로그인해보자

- 맞는 아이디, 비밀번호 입력시

- 좌측에 ~님 환영하였습니다. 가 나타난다.

- menu.jsp 파일에도 세션이 잘 공유되었음을 확인 가능

 

- 틀린 아이디나 비밀번호 입력시

 

포워딩 2가지 방법

- Java Servlet 클래스에서 JSP페이지로 포워딩 방법

1. Dispatcher 방식

RequestDispatcher dispatcher = request.getRequestDispatcher("dispatcher.jsp");
dispatcher.forward(request, response);

- 경로 설정 후 dispatcher 객체로 forward() 함

 

2. Redirect 방식

response.sendRedirect("redirect.jsp");

 

포워딩과 공유 주의

- 세션의 경우엔 어느방식으로 포워딩 되더라도 포워딩된 페이지에서도 세션으로 공유한 값이 유지됨

- request 객체로 공유했을때는, dispatcher 방식으로 포워딩 되어야만 포워딩 된 곳에서 request 객체로 공유한 값을 구해올 수 있다

request.setAttribute("request","requestValue"); // 공유 설정

- request 객체로 공유 설정시 포워딩 방법 2가지 중 Dispatcher 방식의 포워딩만 가능

RequestDispatcher dispatcher = request.getRequestDispatcher("dispatcher.jsp");
dispatcher.forward(request, response);

 

- session 으로 공유 설정시 포워딩 방법 2가지 모두 사용 가능

			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			// 1. Dispatcher 방식으로 포워딩
			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");

 

- Java Servlet 클래스에서 2번 Redirect 방식으로 포워딩 한 후 index.html 실행

- session 으로 공유설정했으므로 어떤 방식으로 포워딩되더라도 session 공유값이 계속 유지됨

 

 

- request 객체로 공유했을때도 두가지 방법으로 포워딩했을때 되는지 실험해보자

- request 객체로 공유시 Dispatcher 방식의 포워딩 방법만 가능

- 그렇게 포워딩해서 View 페이지에 출력하는 것

 

예제 전체 흐름 정리

- index.html 을 실행해서 오른쪽 프레임인 login.jsp 에서 로그인 시도 (왼쪽 프레임 menu.jsp 에는 세션이 없으므로 '로그인'만뜸)

- login.jsp 의 form 의 action 을 통해 "/login" 이 WebServlet 어노테이션 이름값인 곳으로 찾아감 (LoginServlet.java 이다)

- LoginServlet.java 에서 로그인 성공시(id 와 pw 가 "java"일때) loginSuccess.jsp 로 포워딩함

- loginSuccess.jsp 에서 좌측 프레임을 menu.jsp 로 이동함(location.href 로 이동)

- menu.jsp 로 가서 이번엔 세션값이 있으므로 'java 님 환영합니다' 메세지를 띄움


- 예제 ex777 폴더

- DispatcherServlet.java 와 RedirectServlet.java 를 src의 send 패키지로 복사

 

- DispatcherServlet.java

- src 폴더 안에 있다

package send;

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;

/**
 * Servlet implementation class DispatcherServlet
 */
@WebServlet("/DispatcherServlet")
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public DispatcherServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		request.setAttribute("request","requestValue"); // 공유 설정
		
		RequestDispatcher dispatcher = 
				request.getRequestDispatcher("./servlet/ex777/dispatcher.jsp");
		dispatcher.forward(request, response);
	}

}

- 바로 이 DispatcherServlet.java 실행시 디폴트가 get 방식이므로 doGet() 메소드 실행됨

- request 영역으로 공유를 설정하고, dispatcher.jsp 로 포워딩을 하고 있다

- 포워딩시 작성하는 경로값은 WebContent 폴더 하위폴더부터 작성해야한다

- request 영역으로 공유 설정했으므로 포워딩 2가지 방법 중 Dispatcher 방법으로만 포워딩 가능

- forward action tag 대신 Model 2에서는 Dispatcher 방식으로 포워딩 시킴

- 여기서부터 request 영역이 시작된다, 일반적으로 request 영역은 포워딩된 다음페이지까지

+ 생성자는 필요 없다

 

dispatcher 방식으로 포워딩 특징
1. 포워딩된 페이지에서는 request 객체로 공유한 값을 사용할 수 있다.
2. 포워딩이 되면 브라우저의 URL주소가 변경되지 않는다.

- 포워딩을 해도 현재 문서명 그대로 나타나고 url 이 dispatcher.jsp 로 바뀌지 않음

 

- ex777/dispatcher.jsp 에서 공유되는 값을 받아서 잘 출력하고 있다

- dispatcher.jsp

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

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	request 속성 값 : <%=request.getAttribute("request") %>
	</body>
</html>

- request.getAttribute("request") 로 공유값을 잘 받아서 출력하고 있다

 

- DispatcherServlet.java 실행시 doGet()이 실행되고 포워딩하면서 request 로 공유설정함

- DispatcherServlet.java 실행해보자

- 공유값을 잘 가져와서 출력한다

- dispatcher.jsp 로 포워딩(이동)되었지만 URL주소가 dispatcher.jsp 로 바뀌어 있지 않음

 

- 다음은 RedirectServlet.java

package send;

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 RedirectServlet
 */
@WebServlet("/RedirectServlet")
public class RedirectServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RedirectServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		// request 객체로 공유 설정
		request.setAttribute("request", "requestValue");
		
		response.sendRedirect("./servlet/ex777/redirect.jsp");
	}

}

 

- 이번에는 request 객체로 공유 설정 후 Redirect 방식으로 포워딩 하고 있다

- 포워딩은 잘 되지만, request 객체로 공유 설정했을때 Redirect 방식으로 포워딩했다면, 포워딩 된 페이지에서 공유 설정한 값을 가져올 수 없다

- 즉 포워딩은 되지만 포워딩 된 페이지에서 공유값을 가져올 수 없는 문제 생김

- request 객체로 공유 설정시 Dispatcher 방식으로 포워딩해야 포워딩된 페이지에서 공유값 사용 가능

 

- redirect.jsp

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

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	request 속성 값 : <%=request.getAttribute("request") %>
	</body>
</html>

 

redirect 방식으로 포워딩 특징
1. 포워딩된 페이지에서는 request 객체로 공유한 값을 사용할 수 없다.
2. 포워딩된 JSP페이지에서는 자바 서블릿 클래스에서 request 영역의 공유한 속성값에 접근 할 수 없다.

- 공유 영역을 벗어나게되기 때문이다.
3. 포워딩이 되면 브라우저의 URL주소가 포워딩된 JSP 파일명으로 바뀐다.

 

- RedirectServlet.java 파일 실행하자

- 포워딩은 되지만 공유값을 가져오지 못해 null 임

- URL주소가 redirect.jsp 로 변경되어있음

 

MVC 패턴 흐름 정리

- MVC의 Controller 클래스를 Java Servlet Class 로 만들어야한다

- 요청이 찾아오는 것은 WebServlet 어노테이션으로 찾아온다

- DAO 에서 DB에서 검색하고 검색한 값을 Service 클래스에서 공유 수행

- 이때 request 방식으로 공유설정했다면 Dispatcher 방식으로 포워딩 해야함

 

ex)

- Service 클래스에서 페이징에 필요한 6개 변수를 request 객체로 공유 설정

- View 페이지에서 결과를 EL, JSTL 로 출력

 


표현 언어 (Express Language)

- 기존 Model 1 표현식 태그의 역할을 대신함

- JSP의 표현식 태그와 비슷한 역할을 수행한다

- Spring, Spring Boot 에서도 사용

 

- 표현언어는 ${ } 기호를 사용한다, 이 사이에 코드를 씀

- 표현언어에 JSP의 네가지 영역 객체로 공유한 값을 구해올 수 있다.

ex) ${ sessionScope.id } // sessionScope는 내장 객체 중 하나

- 표현언어에 산술연산자(+,-,*,/,%), 비교연산자(>,>=,<,<=,==,!=), 논리연산자(&&, ||, !), 조건연산자, empty 연산자 등을 사용할 수 있다. ex) ${ 10 + 20 }

- 표현언어에 자바빈 클래스의 getter 메소드를 호출할 수 있다.

ex) ${ article.id } // article 은 DTO 객체, article.getId() 와 같은 의미

 

- Service 클래스에서 request 객체로 공유 설정한 뒤,

- Controller 클래스를 통해 View 페이지로 출력할때 getParameter() 이나, 형변환 등을 하지 않아도바로 결과를 EL로 출력 가능

- EL 과 JSTL 이 View 페이지에서 결합되어 결과를 출력함

 

EL 지원되는 내장 객체

- EL 안에서만 사용 가능, 즉 ${} 안에서만 사용 가능

 

1. param

${param.name}

- name 은 값을 전달하기위한 변수명

- 아래의 코드와 같다

<%=request.getParamter("name")%>

 

2. sessionScope

${sessionScope.id}

- id 는 세션 네임

- 아래의 코드와 같다

<%= session.getAttribute("id")%>

 

표현 언어 예제

실습 준비

- 클라우드의 el 폴더를 jsppro 의 WebContent 폴더에 복붙

 

표현 언어 예제 1

- el/ex11 폴더의 eLEx1.jsp 파일을 보자

- eLEx1.jsp

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

<HTML>
<HEAD>
	<TITLE>간단한 표현언어(EL)예제</TITLE>
</HEAD>

<BODY>

<H3>간단한 표현언어(EL)예제</H3>
<P>
연산자를 사용한 예와 내장객체의 사용한 예:

<TABLE BORDER="1">
  <THEAD>
    <TD><B>표현식</B></TD>
    <TD><B>값</B></TD>
  </THEAD>
  <TR>
    <TD>\${2 + 5}</TD>
    <TD>${2 + 5}</TD>
  </TR>
  <TR>
    <TD>\${4/5}</TD>
    <TD>${4/5}</TD>
  </TR>
  <TR>
    <TD>\${5 div 6}</TD>
    <TD>${5 div 6}</TD>
  </TR>
  <TR>
    <TD>\${5 mod 7}</TD>
    <TD>${5 mod 7}</TD>
  </TR>
  <TR>
    <TD>\${2 < 3}</TD>
    <TD>${2 < 3}</TD>
  </TR>
  <TR>
    <TD>\${2 gt 3}</TD>
    <TD>${2 gt 3}</TD>
  </TR>
  <TR>
    <TD>\${3.1 le 3.2}</TD>
    <TD>${3.1 le 3.2}</TD>
  </TR>
  <TR>
    <TD>\${(5 > 3) ? 5 : 3}</TD>
    <TD>${(5 > 3) ? 5 : 3}</TD>
  </TR>
  <TR>
    <TD>\${header["host"]}</TD>
    <TD>${header["host"]}</TD>
  </TR>
  <TR>
    <TD>\${header["user-agent"]}</TD>
    <TD>${header["user-agent"]}</TD>
  </TR>
</TABLE>
</BODY>
</HTML>

- EL 안에서 각종 연산자를 사용하는 내용이다

- EL ${} 안에 산술연산자, 비교연산자, 논리연산자, 조건연산자, empty 연산자 등을 쓸 수 있다

 

브라우저에 그대로 출력 vs 연산해서 출력 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 + 5}</TD>
    <TD>${2 + 5}</TD>
  </TR>

- \(역슬래시) 가 있으면 \ 다음의 내용을 특수문자로 인식해서 브라우저에 그대로 출력함

- ${2 + 5} 면 연산결과 7 이 출력됨

 

나누기 연산시 결과 주의 1 (eLex1.jsp 부분)

  <TR>
    <TD>\${4/5}</TD>
    <TD>${4/5}</TD>
  </TR>

- 자료형이 따로 없기떄문에 4/5 연산을 수행시 정수형으로 결과가 나오지 않고, 0.8 실수로 출력됨

 

나누기 연산시 결과 주의 2 (eLex1.jsp 부분)

  <TR>
    <TD>\${5 div 6}</TD>
    <TD>${5 div 6}</TD>
  </TR>

- / 기호 대신 div 를 적어도 같은 역할, 5/6 과 같다

- 마찬가지로 0.83333.. 실수로 결과가 나옴

 

비교 연산 1 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 < 3}</TD>
    <TD>${2 < 3}</TD>
  </TR>

- 결과는 true 또는 false

 

비교 연산 2 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 gt 3}</TD>
    <TD>${2 gt 3}</TD>
  </TR>
  <TR>
    <TD>\${3.1 le 3.2}</TD>
    <TD>${3.1 le 3.2}</TD>
  </TR>

- 부등호 모양의 비교연산자를 사용해도 되고, gt (greater than) 또는 lt(less than) 을 사용해도 된다

- 2 gt 3 은 2 > 3 과 같은 의미임

 

삼항 연산자 (eLex1.jsp 부분)

  <TR>
    <TD>\${(5 > 3) ? 5 : 3}</TD>
    <TD>${(5 > 3) ? 5 : 3}</TD>
  </TR>

- 첫번째 식이 참이면 5가 , 거짓이면 3 이 결과가 됨

 

header["user-agent"] (eLex1.jsp 부분)

  <TR>
    <TD>\${header["user-agent"]}</TD>
    <TD>${header["user-agent"]}</TD>
  </TR>

- 브라우저에 대한 정보를 가져옴

 

- eLEx1.jsp 실행시

- 연산자들의 연산결과가 출력된다.

 

표현 언어 예제 2

- WebContent/el/ex22/eLEx2.jsp

<%@ page contentType="text/html;charset=utf-8"%>
<% request.setCharacterEncoding("utf-8");%>

<HTML>
<HEAD>
<TITLE>표현언어의 사용예제2</TITLE>
</HEAD>

<BODY>

<H3>표현언어의 사용예제2 -파라미터값 처리</H3>
<P>
<FORM action="eLEx2.jsp" method="post">
   이름 : <input type="text" name="name" value="${param['name']}">
          <input type="submit" value="확인">
</FORM>

<P>
이름은: <%=request.getParameter("name") %> <br>
이름은: ${param.name} 입니다. <br>
이름은: ${param['name']} 입니다.

</BODY>
</HTML>

- action 이 자기 자신으로 되어있다

- 원래 이름값을 받을때는 request.getParameter() 로 받아야하지만, EL 을 사용해서 %{param.name} 으로 더 간단하게 값을 받고 출력까지 시켜줌

+ 한글값 깨지지 않도록 가장 위에서 한글값 인코딩을 하고 있다

- 실행시 이름 입력 양식이 나타나고 '확인'누르면 자기 자신의 파일을 다시 실행해서 다시 입력창을 띄움, 그리고 받은 이름값을 아래에 출력

 

EL 내장 객체 param

이름은: <%=request.getParameter("name") %> <br>
이름은: ${param.name} 입니다. <br>
이름은: ${param['name']} 입니다.

- 세 줄이 모두 같은 코드

- EL 은 표현식 태그 대신이므로 출력까지 시킴

 

+ EL 내장 객체 sessionScope

+ Session 영역 공유 설정시에는 기존처럼 한다, setAttribute() 사용

- Session 영역 공유값 받아오기는 sessionScope 내장객체로 쉽게 가능

${sessionScope.id}

- 아래와 같다

<%=session.getAttribute("id")%>

 

+ EL 내장 객체 requestScope

- Request 영역 공유값 받아오기를 requestScope 내장객체로 쉽게 가능

${requestScope.page}

- 이렇게 써도 되지만 이렇게 requestScope 를 생략해도 바로 ${page} 로 출력 가능 하므로 잘 쓰지 않음

${page}

- 두 코드는 아래와 같다

<%=request.getAttribute("page")%>

 

+ EL 은 JSTL 과 결합되어 사용된다

+ 지금 다 설명하지 못한 EL 기능도 JSTL 하면서 같이 할 것

 

표현 언어 예제 3

실습 준비

- WebContent/el/ex33 의 Product.java 는 자바파일이므로 src 폴더 하위에 jspbook 패키지를 만들어서 그곳으로 복사

 

표현 언어 예제 3

- src/jspbook/Product.java

package jspbook;

public class Product {

	// 상품 목록을 저장할 배열
	private String[] productList = {"item1","item2","item3","item4","item5"};
	
	// 변수 설정
	private int num1 = 10;
	private int num2 = 20;
	
	public int getNum1() {
		return num1;
	}

	public int getNum2() {
		return num2;
	}

	public String[] getProductList() {
		return productList;
	}
}

- 메소드를 통해서 Product 클래스의 필드들을 돌려주고 있음

- 일종의 DTO 클래스이다.

 

- WebContent/el/ex33/productList.jsp

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

<html>

	<head>
		
<meta charset="utf-8">

		<title>EL 예제</title>

	</head>

	<body>

	<center>
		<H2>EL 예제-상품목록</H2>
		<HR>
	<form method="post" action="productSel.jsp">
		<jsp:useBean id="product" class="jspbook.Product" scope="session"/>
		<select name="sel">
			<%
			// session 에서 가져온 Product 객체로 부터 저장되어 있는 목록을 가져와 출력함.
			for(String item : product.getProductList()) {
				out.println("<option>"+item+"</option>");
			}
			%>
		</select>
		<input type="submit" value="선택"/>
	</form>
	</center>
</body>

</html>

- Model 1 으로 되어있다, action 에서 productSel.jsp 파일로 값을 전달함

- useBean action tag 를 써서 Product 객체 product 를 생성, 생성된 객체를 scope 속성을 통해서 사용 범위 지정

- scope 속성으로는 영역명이 들어가는데, 여기선 session 이 적혀있으므로, 세션이 끊기기 전까지 이 객체가 계속 공유됨

- scope 속성값 생략시 기본값이 page, 여기서 session 이므로 다음 페이지 productSel.jsp 로 넘어가도 세션이 끊기기 전까지 이 객체 product 를 계속 쓸 수 있다.

- 객체 product 에서 getProductList() 메소드를 사용해서 String형 배열을 가져옴

- 향상된 for 문으로 getProductList() 에서 "item1", "item2", "item3".. 를 차례로 가져와서 item 변수에 저장, 그 item 변수를 차례로 출력하고 있음

- option 태그 안에 value 속성이 없으면사이의 값이 select 의 name 인 sel 에 저장되어 넘어감

- 객체의 scope 값이 session 으로 되어있으면 session 으로 공유된 것과 같다

+ 향상된 for 문 오른쪽에는 배열이나 List 가 온다

 

- productList.jsp 실행시

- 'item3' 선택 후 '선택' 버튼 클릭시

 

- WebContent/el/ex33/productSel.jsp

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

<html>
	<head>		
		<meta charset="utf-8">
		<title>EL 예제</title>
	</head>
	<body>
		<center>
		<H2>EL 예제-상품선택</H2>
		<HR>
		<!-- 표현 언어로 출력 -->
		1. 선택한 상품은 : ${param.sel} <br>
		
		<!-- 표현식 태그로 출력 -->
		1. 선택한 상품은 : <%=request.getParameter("sel") %> <br>
		
		<!-- 표현 언어로 출력 -->
		2. num1 + num2 = ${product.num1 + product.num2} <br>
		
		<!-- 표현식 태그로 출력 -->
<%
		Product pro = (Product) session.getAttribute("product");
%>
		2. num1 + num2 = <%=pro.getNum1() + pro.getNum2()%>
		
		</center>
	</body>
</html>

- 페이지가 넘어왔지만 세션이 끊어지기전까지 객체 product 는 계속 사용가능하므로 이 페이지에서도 Product 객체 product 사용 가능

- ${param.sel} 과 request.getParamteter("sel") 로 select 옵션의 선택된 값을 구해올 수 있다, 같은 의미이다.

+ productList.jsp 에서 option 태그 안에 value 속성이 없으므로 사이의 값인 "item1", "item2", "item3" ..중 선택한 것이 sel 변수에 저장되어서 넘어옴

 

주의 1 (productSel.jsp 부분)

		<!-- 표현 언어로 출력 -->
		2. num1 + num2 = ${product.num1 + product.num2} <br>

- 객체 product 의 num1, num2 값을 product.num1, product.num2 로 구해오는데, Product 클래스에서 num1, num2 는 private 이다

- 표기만 product.num1 으로 필드명으로 표기하고, 의미는 product.getNum1() 을 리턴받아 출력한다는 의미

- EL 안에서는 메소드를 표기하지 않음

- 즉 product.num1 은 표기일 뿐 실질적으로는 product.getNum1() 을 의미한다.

- 아래의 코드와 위의 코드는 같다.

 

주의 2 (productSel.jsp 부분)

		<!-- 표현식 태그로 출력 -->
<%
		Product pro = (Product) session.getAttribute("product");
%>
		2. num1 + num2 = <%=pro.getNum1() + pro.getNum2()%>

- 위의 코드와 같다

- 객체의 scope 값이 session 으로 되어있으면 session 으로 공유된 것과 같다

- 공유 네임값도 그 객체 이름으로 되어있다.

- getAttribute("product") 로 가져올때 다운캐스팅을 해야한다

 

- productList.jsp 에서

		<jsp:useBean id="product" class="jspbook.Product" scope="session"/>

- 이렇게 product 객체를 만들면서 scope 를 "session" 으로 설정했다

- EL 태그에서는 바로 ${product.num1} 으로 사용 가능하다

- EL 이 아닌 표현식 태그로 다른 페이지에서 이 객체 product 를 사용하려면 session 에 공유된 값으로부터 객체를 가져와서 사용해야 한다.

- 위의 useBean action tag 중 scope="session" 부분은 아래 코드와 같다

session.setAttribute("product");

- 객체 product 가 session 영역에 공유 설정되어있는 것이므로, EL 에서는 바로 사용 가능

 

표현 언어 (EL) 정리

- 표현 언어는 현재 공유된 값을 출력하는 것이다!

- 현재 페이지에서 공유된 값을 사용할 수 있으면 EL 태그로 쉽게 출력할 수 있다.

ex) 이전 페이지에서 session 영역에 어떤 값을 공유 설정해둠, 다음 페이지에서 그 값을 구해올 수 있다, 이때 EL 태그로 쉽게 출력 가능

 

String str = "jsp";

표현식 태그  :  <%=str %>

	      <%=article.getId()%>

	     <%=session.getAttribute("test")%>

	     <%=request.getParameter("name")%>

	     <%=request.getParameterValues("choice")%>


request.setAttribute("str",str);

표현언어(EL) : ${str}

	      ${article.id}

	      ${sessionScope.test}

	      ${param.name}

	      ${paramValues.choice}

<표현식 태그>

- 단순히 String str = "jsp" 일때, ${str} 로 "jsp" 를 출력하는 것도 가능하다.

<표현언어(EL)>

- 표현 언어는 현재 공유된 값을 출력하는 것이다!

- 단순히 String str = "jsp" 일때, ${str} 로 "jsp" 를 출력하는 것은 불가능하다

 

- str 와 article 이 request 객체로 공유가 되어야만 ${str}, ${article.id} 을 EL로 이렇게 출력 가능하다

ex) request.setAttribute("str",str) 로 공유되어있어야만 공유 네임 "str" 로 ${str} 사용 가능

- article 또한 request 객체로 공유되어있어야만 ${article.id} 사용 가능

- ${article.id} 는 <표현식 태그>의 <%=article.getId()%> 와 같은 의미

- ${sessionScope.test} 는 표현식 태그의 <%=session.getAttribute("test")%> 와 같다

- sessionScope 내장객체로 쉽게 session 의 공유값을 가져오는 것이다.

 

+ 또한 get/post 방식으로 전달되는 값을 받을때도 param 내장객체를 이용해서 쉽게 받을 수 있다.


+ Session 이 끊어지는 때

1. 세션을 강제로 invalid()로 끊을때

2. 브라우저 창을 닫을때 Session 끊김

복습

첨부파일 업로드 위치

- 실제 업로드 되는 폴더 위치가 따로 있다

- 이클립스에 폴더를 만들어둬야 이 위치에 폴더가 생긴다

 

첨부파일 업로드 폼

- form 태그 안에 enctype="multipart/form-data" 속성이 있어야만 한다

- action 으로 지정된 페이지에서 실제 업로드를 진행한다

 

첨부파일 업로드

- MultipartRequest 객체를 생성하면 자동으로 업로드가 수행된다

- 객체를 생성하며 생성자에서 인코딩을 수행하므로 post 로 전달된 값이 있더라도 인코딩 할 필요 없다

- DefaultFileRenamePolicy 객체가 파일명뒤에 번호를 붙이며 중복문제를 해결해 준다

 

+ 첨부파일과 DB

- 직접 첨부파일을 저장할 수 있는 컬럼이 있다

- 일반적으로는 서버측의 폴더에 첨부파일이 저장되고 테이블에는 첨부파일명만 저장됨

 

+ 첨부파일과 스프링

- 스프링에서는 cos 외에 다른 라이브러리를 사용해서 첨부파일을 업로드할것


자료실 게시판 : 파일별 기능 및 구조 설명

- writeForm.jsp 에 첨부파일을 선택하는 양식이 포함되어있다

- writeForm.jsp는 자료실 기능을 가진 게시판 폼

 

자료실 게시판 : 글 작성 & 첨부파일 업로드

- 첨부파일을 업로드 가능한 글 작성 폼인 writeForm.jsp 파일 부터 작성하자

- writeForm.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<html>
<head>
	<title>게시판</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="check.js"></script>
</head>   

<body bgcolor="<%=bodyback_c%>">  
<center><b>글쓰기</b>
<br>
<form method="post" name="writeform" action="writePro.jsp" enctype="multipart/form-data">

<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input autofocus type="text" size="10" maxlength="10" id="writer" name="writer"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">    
       <input type="text" size="40" maxlength="50" id="subject" name="subject"></td>	
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" name="email" ></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">파일첨부</td>
    <td  width="330">
       <input type="file" size="40"  name="upload" ></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"></textarea> </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글쓰기" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="location.href='list.jsp'">
</td></tr></table>    
   
</form>      
</body>
</html>

- 폼을 통해 writePro.jsp 파일로 값들을 전달하고 있다

- 양식의 name 값들은 DTO 클래스의 프로퍼티명과 같고, 테이블 안 컬럼명과도 같다

 

첨부파일 양식 (writeForm.jsp 부분)

- 첨부파일은 input type = "file" 로 만들어야하고, name 값을 자세히 봐야 한다

 

폼을통해 첨부파일 전송 시 주의사항 (writeForm.jsp 부분)

1. method 가 post 방식이어야 한다

2. form 태그 안에 enctype="multipart/form-data" 가 있어야한다

 

 

- 다음은 action tag 로 지정된, 실제로 서버에 글을 작성하고 첨부파일을 업로드 하는 writePro.jsp 파일을 생성 및 작성하자

- writePro.jsp

<%@page import="upload.BoardDBBean"%>
<%@page import="upload.BoardDataBean"%>
<%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
<%@page import="com.oreilly.servlet.MultipartRequest"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// 업로드할 디렉토리 위치 구하기
	String path = request.getRealPath("upload");
	System.out.println("path:" + path);
	
	// 첨부파일의 크기 설정(단위 : Byte) : 1MB
	int size = 1024 * 1024;
	
	// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 업로드가 수행된다.
	MultipartRequest multi = 
		new MultipartRequest(request,
				path,	// 업로드 디렉토리 위치
				size,	// 첨부파일의 크기 : 1MB
				"utf-8",	// 인코딩 타입 설정
				new DefaultFileRenamePolicy());	// 중복 문제 해결
				
	String writer = multi.getParameter("writer");
	String subject = multi.getParameter("subject");
	String email = multi.getParameter("email");
	String content = multi.getParameter("content");
	String passwd = multi.getParameter("passwd");
	
	// 오리지널 파일명 : 클라이언트가 업로드한 파일명
	String upload0 = multi.getOriginalFileName("upload");
	
	// 실제 서버에 저장된 파일명
	String upload = multi.getFilesystemName("upload");
	
	BoardDataBean board = new BoardDataBean();
	board.setWriter(writer);
	board.setEmail(email);
	board.setSubject(subject);
	board.setContent(content);
	board.setPasswd(passwd);
	board.setIp(request.getRemoteAddr());	// 클라이언트의 IP 주소
	board.setUpload(upload);	// 첨부 파일명
	
	BoardDBBean dao = BoardDBBean.getInstance();
	int result = dao.insert(board);	// insert SQL문 실행
	
	if(result == 1) {
%>
		<script>
			alert("글 작성 성공");
			location.href = "list.jsp";
		</script>
<%	} else {%>
		<script>
			alert("글 작성 실패");
			history.go(-1);
		</script>
<%	}%>

- 첨부파일이 전송되면, 지금까지 쓰던 action tag 들로 쉽게 처리 불가

- MultipartRequest 객체를 생성하고, 그 객체로 getParameter() 메소드를 사용해서 나머지 값들을 받아야함

 

- 순서 잘 보기

1. 업로드할 디렉토리 위치 구하기 (writePro.jsp 부분)

	// 업로드할 디렉토리 위치 구하기
	String path = request.getRealPath("upload");
	System.out.println("path:" + path);

- 폴더명을 적어서 그 업로드될 디렉토리의 진짜 경로를 getRealPath() 로 구한다

- 업로드를 수행하게 되면 path 값이 콘솔창에 출력된다, 그 폴더로 들어가면 파일들이 있음

 

2. 첨부파일의 크기(단위: Byte) 설정 (writePro.jsp 부분)

	// 첨부파일의 크기 설정(단위 : Byte) : 1MB
	int size = 1024 * 1024;

- 1024 로 하면 1KB 가 되고 1024 * 1024 로 하면 1MB 가 됨, 현재는 1MB로 설정

- 이 범위 벗어나면 업로드 되지 않음

 

3. 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 업로드가 수행됨 (writePro.jsp 부분)

	// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 업로드가 수행된다.
	MultipartRequest multi = 
		new MultipartRequest(request,
				path,	// 업로드 디렉토리 위치
				size,	// 첨부파일의 크기 : 1MB
				"utf-8",	// 인코딩 타입 설정
				new DefaultFileRenamePolicy());	// 중복 문제 해결

- 첫번째 매개변수는 request, 두번째 매개변수는 path, 세번째 매개변수는 size, 네번째 매개변수는 인코딩 타입

- 다섯번째 매개변수는 중복문제를 해결할 DefaultFileRenamePolicy 객체

- cos 라이브러리는 따로 첨부파일을 전송하는 코드는 없고, MultipartRequest 객체를 만들면 자동 업로드됨

- 여기서 인코딩을 처리하므로 이 파일 wirtePro.jsp 에서는 따로 인코딩 시켜주는 코드가 필요없다

 

+ import 정보 (writePro.jsp 부분)

<%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
<%@page import="com.oreilly.servlet.MultipartRequest"%>

 

4. 전달된 값 받기 (writePro.jsp 부분)

	String writer = multi.getParameter("writer");
	String subject = multi.getParameter("subject");
	String email = multi.getParameter("email");
	String content = multi.getParameter("content");
	String passwd = multi.getParameter("passwd");

- 이름, 제목, 내용 등을 받기 위해 기존에는 request.getParameter() 나 useBean, setProperty action tag 를 사용했다

- 첨부파일 전송이 포함되어있을때는 첨부파일 외의 값을 받을때 이 메소드나 액션태그들을 사용 불가

- MultipartRequest 클래스에 있는 메소드들은 MutlipartRequest 객체 메소드를 사용해야한다

ex) getParameter(), getFilesystemName(), getFileNames()

- MultipartRequest 클래스에 없는 메소드여야 request 객체의 메소드를 사용가능하다

ex) getRemoteAddr()

 

5. 두가지 파일명 구하기 (writePro.jsp 부분)

	// 오리지널 파일명 : 클라이언트가 업로드한 파일명
	String upload0 = multi.getOriginalFileName("upload");
	
	// 실제 서버에 저장된 파일명
	String upload = multi.getFilesystemName("upload");

- 실제 서버에 저장된 파일명과 사용자가 업로드한 오리지널 파일명이 다를 수 있다

- 중복이 발생된 경우에 두 파일명이 다르다, 두 파일명을 구해야함

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

 

6. DTO 객체 생성 및 값 설정 (writePro.jsp 부분)

	BoardDataBean board = new BoardDataBean();
	board.setWriter(writer);
	board.setEmail(email);
	board.setSubject(subject);
	board.setContent(content);
	board.setPasswd(passwd);
	board.setIp(request.getRemoteAddr());	// 클라이언트의 IP 주소
	board.setUpload(upload);	// 첨부 파일명

- 앞에서 모든 값들을 변수에 저장시켜뒀으니 DTO 객체 board를 생성해서 값을 넣어야한다

- useBean action tag, setProperty action tag 가 사용불가하므로, 직접 객체를 생성 후 setter 메소드로 값 설정

- ip 주소는 폼에서 넘어오지 않으므로 따로 구해서 저장한다

- 두가지 파일명이 있는데 파일명을 저장할 컬럼은 upload 하나의 컬럼이고, DTO 프로퍼티도 하나이다

- 두가지 파일명 중 하나를 선택해서 저장해야함

- 우리는 실제 서버에 저장된 파일명을 테이블의 upload 컬럼에 저장할것

 

7. DAO 객체 생성 후 DAO 의 메소드 사용 (writePro.jsp 부분)

	BoardDBBean dao = BoardDBBean.getInstance();
	int result = dao.insert(board);	// insert SQL문 실행

- DAO의 insert() 메소드를 사용한다

- 매개변수를 통해 insert 할 DTO 객체 board를 전달

 

8. insert 성공, 실패 처리 (writePro.jsp 부분)

	if(result == 1) {
%>
		<script>
			alert("글 작성 성공");
			location.href = "list.jsp";
		</script>
<%	} else {%>
		<script>
			alert("글 작성 실패");
			history.go(-1);
		</script>
<%	}%>

 

- 이제 DAO 클래스에서 insert() 메소드를 작성하자

 

DAO 클래스 글 작성 메소드 작성

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 insert() 부분 코드만

	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into upload values(upload_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// readcount : 조회수
			pstmt.setString(6, board.getContent());
			pstmt.setString(7, board.getIp());
			pstmt.setString(8, board.getUpload());
			
			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;
	}

- 매개변수에 객체를 넘겨줌으로서 주솟값을 넘겨서 호출 Call By Reference

- JSP 파일에서 DAO 에 접근하기 위해서는 다른 패키지에 있으므로 클래스와 메소드가 public 이어야 가능

- 삽입 성공 데이터 개수를 돌려줄 것이므로 리턴자료형 int, 일반적으로 삽입은 1번만 가능하므로 1 반환

+ 기본자료형은 num 과 readcount 는 폼에서 넘어오지 않더라도 초기값이 0 으로 설정됨,

+ getReadCount() 로 0 을 불러오게됨, 직접 0 으로 설정해줘도 된다

 

- writeForm.jsp 에서 작성 및 첨부파일 업로드 하고 작성 시도

 

데이터 insert 확인

- sql 파일에서 확인해보면

- 데이터가 잘 insert 되었다

 

첨부파일 업로드 확인

- 콘솔창 확인해보면 path 값이 찍혀있다, 이 경로로 가보기

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

+ 앞의 upload 는 프로젝트명, 뒤의 upload 는 폴더명

- 탐색기에서 확인하면 첨부파일이 잘 업로드 되었음을 확인 가능하다

 

- 다음으로 list.jsp 를 구현해야함

 

자료실 게시판 : 목록 페이지 / 게시판 페이지

- list.jsp

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.util.List"%>
<%@page import="upload.BoardDataBean"%>
<%@page import="upload.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null){
		pageNum = "1";		// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page 데이터를 추출할 시작번호
	// endRow : 각 page 데이터를 추출할 끝번호
	// 1 page : startRow=1,  endRow=10
	// 2 page : startRow=11, endRow=20
	// 3 page : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count:"+count);
	
	List<BoardDataBean> list = null;
	if(count > 0){
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list:"+list);
%>      
    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
	if(count == 0){
%>
		작성된 글이 없습니다.
<%	}else{ %>
	
		<a href="writeForm.jsp">글작성</a> 글갯수 : <%=count %>
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>작성자</th>
				<th>작성일</th>
				<th>조회수</th>
				<th>IP주소</th>
			</tr>
<%
			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage-1) * page_size;

			SimpleDateFormat sd = 
					new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			
			for(int i=0; i<list.size(); i++){
				BoardDataBean board = list.get(i);
%>				
			<tr>
				<td><%=number-- %></td>
				<td>
				
<a href="content.jsp?num=<%=board.getNum()%>&page=<%=currentPage%>">				
				<%=board.getSubject() %>
</a>	
			
				</td>
				<td><%=board.getWriter() %></td>
				<td><%=sd.format(board.getReg_date()) %></td>
				<td><%=board.getReadcount() %></td>
				<td><%=board.getIp() %></td>
			</tr>			
				
<%			} // for end
%>
			
		</table>	
		
<%	} // else end
%>

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0){
	
	// pageCount : 총페이지 수
	int pageCount=count/page_size + ((count%page_size==0) ? 0:1);
	System.out.println("pageCount:"+pageCount);
	
	// srartPage : 각 블럭의 시작 페이지 번호    1, 11, 21...
	// endPage : 각 블럭의 끝 페이지 번호         10, 20, 30...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에는 endPage값을 pageCount로 설정
	if(endPage > pageCount){
		endPage = pageCount;
	}
%>	
	<!-- 1페이지로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>

<%
	// 이전 블럭으로 이동
	if(startPage > 10){
%>	
		<a href="list.jsp?page=<%=startPage-10%>">[이전]</a>	
<%	}	

	//각 블럭당 10개의 페이지 출력
	for(int i=startPage; i<=endPage; i++){
		if(i==currentPage){ 	// 현재 페이지	 %>
				[<%=i%>]
<% 		}else{ %>
				<a href="list.jsp?page=<%=i%>">[<%=i%>]</a>
<% 		}	
	} // for end

	// 다음 블럭으로 이동하는 부분
	if(endPage < pageCount){ %>
			<a href="list.jsp?page=<%=startPage+10%>">[다음]</a>		
<%	}  %>
	
	<!-- 마지막 페이지로 이동 -->
		<a href="list.jsp?page=<%=pageCount%>"
			style="text-decoration:none"> >> </a>			
<%	
}
%>
</center>

</body>
</html>

- 페이징 처리를 하고 있다, 페이징 처리 자세한 내용은 예전에 했다

https://laker99.tistory.com/116

https://laker99.tistory.com/117

 

- DAO 클래스에서 총 데이터 개수를 구해주는 메소드 getCount() 와 목록을 구해주는 메소드 getList() 를 구현해야함

 

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

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.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 upload";
			
			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;
	}

- 총 데이터 개수를 숫자로 돌려주므로 리턴자료형 int

- DB의 전체 데이터개수를 구해주는 메소드이므로 매개변수는 필요없다

- select sql 문을 사용해야하므로 결과를 돌려줄 ResultSet 객체 rs 가 필요

- 데이터가 1개이므로 if 문을 사용하고, rs.getInt(1) 또는 rs.getInt("count(*)") 로 데이터를 가져옴

 

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

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 getList() 부분 코드만

	// 게시판 목록 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, upload.* from *";
			sql += " (select * from upload order by num desc) upload)";
			sql += " where rnum >= ? and rnum <= ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, start);
			pstmt.setInt(2, end);
			
			rs = pstmt.executeQuery();	// SQL문 실행
			
			while(rs.next()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				board.setUpload(rs.getString("upload"));
				
				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;
	}

- 사용자가 1페이지 선택시 1 과 10 이 매개변수로 넘어온다, 매개변수는 int 두개

- 2개이상의 데이터를 검색하는 메소드의 리턴 자료형은 List

- List 객체를 생성하고 그 객체에 글들(데이터들)을 저장하고 그 List 를 반환할 것

- select sql 문을 사용해야하므로 결과를 돌려줄 ResultSet 객체 rs 가 필요

- 데이터를 여러개 검색하므로 while 문 안에서 rs.next() 로 컬럼별로 잘라서 데이터를 가져오게한다

- 하나의 데이터를 저장하기 위한 DTO 객체를 생성하고, setter 메소드로 rs 에서 가져온 값을 세팅한 후 그 DTO 객체를 list 안에 저장

<SQL문>

- 서브쿼리가 2개이다

- 두번째 서브 쿼리에서 최근글이 위로가도록 내림차순 정렬하고, 두번째 서브쿼리의 별칭을 upload 로 지정

- 첫번째 서브쿼리에 upload.* 를 사용해서 두번째 서브쿼리의 모든 컬럼을 사용

- 첫번째 서브쿼리에서 rownum 컬럼의 별칭을 rnum 으로 지정

- 바깥쪽 쿼리에서 rnum 의 범위를 지정

 

- list.jsp 를 실행해서 목록 페이지가 잘 구현되었는지 확인해보자

+ 화면에 출력되는 번호값은 num 컬럼과 관련 없다, 이 번호값은 파생된 변수

+ 나중에 갤러리 게시판 처럼 화면에 사진을 출력할 수도 있다

 

- 콘솔창 확인

- 총 데이터 개수를 의미하는 count 는 1

- list 안에 있는 DTO 객체, 즉 데이터의 주솟값이 출력되고있다

- 총 페이지수를 의미하는 pageCount 는 1

 

자료실 게시판 : 상세 페이지

- list.jsp 에서 상세페이지로 넘어가는 부분 코드

- 글 번호와 페이지 번호가 content.jsp 로 넘어간다

 

- 다음은 제목 링크를 클릭시 넘어가는 상세페이지 content.jsp 를 생성 및 작성하자

- content.jsp

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="upload.BoardDataBean"%>
<%@page import="upload.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 조회수 1 증가 + 상세 정보 구하기
	BoardDataBean board = dao.updateContent(num);
	
	SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	String content = board.getContent().replace("\n","<br>");

%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=500 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>번호</td>
		<td><%=board.getNum() %></td>
		<td>조회수</td>
		<td><%=board.getReadcount() %></td>
	</tr>
	<tr>
		<td>작성자명</td>
		<td><%=board.getWriter() %></td>
		<td>작성일</td>
		<td><%=sd.format(board.getReg_date()) %></td>
	</tr>
	<tr>
		<td>제목</td>
		<td colspan=3><%=board.getSubject() %></td>
	</tr>
	<tr>
		<td>내용</td>
		<td colspan=3>
			<pre><%=board.getContent() %></pre>
			<%=content %>
		</td>
	</tr>
	<tr>
		<td>첨부파일<td>
		<td colspan=3>
			<!-- 첨부 파일이 있으면 -->
			<% if(board.getUpload() != null) {	%>
				<a href="file_down.jsp?file_name=<%=board.getUpload() %>">
					<%=board.getUpload() %>
				</a>	
			<% } %>
		</td>
	</tr>
	<tr>
		<td colspan=4 align=center>
			<input type="button" value="글수정"
			onClick="location.href='updateForm.jsp?num=<%=num%>&page=<%=nowpage%>'">
			<input type="button" value="글삭제"
			onClick="location.href='deleteForm.jsp?num=<%=num%>&page=<%=nowpage%>'">
			<input type="button" value="글목록"
			onClick="location.href='list.jsp?page=<%=nowpage%>'">
		</td>
	</tr>
</table>
</body>
</html>

- 일단 넘어온 글 번호 num 값과 페이지 번호 page 값을 받아야한다

- 글 번호 num 은 상세 정보를 구해올때 select sql 문에서 사용되어야하므로 바로 int 형으로 형변환한다

- 상세페이지에서 해야하는 일 : 조회수 1 증가 + 상세정보 구하기

- 조회수 1 증가 & 상세 정보 구하는 메소드 updateContent() 호출, 매개변수로 글번호 num 전달하고 1개 데이터 상세정보를 저장한 DTO 객체를 리턴받기

- 돌려받은 객체를 뿌려주고 있다

- SimpleDateFormat 객체, replace() 메소드 등 사용

- 마지막 버튼 3가지 중 '글수정' 과 '글삭제' 버튼에는 글번호 num 값과 페이지번호 page 값을 전달하고 있다

 

첨부파일 다운 (content.jsp 부분)

- 조건식을 써서 첨부파일이 있는 경우만 첨부파일명을 출력

- 첨부파일명을 클릭시 file_down.jsp 로 링크를 걸어서 이동하면서 파일명을 전달함

- 첨부파일명 좌우에 file_down.jsp 로의 링크를 걸어서 해당 첨부파일을 다운로드 가능하게함

 

DAO 클래스 조회수 1 증가 & 상세 정보 구하기 메소드 작성

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 updateContent() 메소드 부분 코드만

	// 조회수 1 증가 + 상세 정보 구하기
	public BoardDataBean updateContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update upload set readcount = readcount + 1 ";
			sql += "where num = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();
			
			sql = "select * from upload where num = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				board.setUpload(rs.getString("upload"));
			}
		} 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 board;
	}

- 1개 데이터의 상세정보를 리턴하므로 리턴자료형은 DTO

- 글 번호 num 을 넘겨받아야하므로 매개변수 자료형은 int

- 조회수 1 증가시에는 update 문, 상세 정보 구하기 에는 select 문 사용

 

- 확인하기 위해 목록페이지 list.jsp 로 가서 글 제목을 클릭해서 상세페이지 content.jsp 로 가보자

- 파일명 링크 클릭시 다운로드가 진행됨

- 글수정을 누르면 updateForm.jsp 파일로, 글삭제는 deleteForm.jsp 파일로, 글목록은 list.jsp 파일로 이동

 

자료실 게시판 : 글 수정

- 다음은 글 수정을 하는 수정 폼 updateForm.jsp 를 생성 및 작성하자

- updateForm.jsp

<%@page import="upload.BoardDataBean"%>
<%@page import="upload.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>

<html>
<head>
	<title>게시판</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="check.js"></script>
</head>   

<body bgcolor="<%=bodyback_c%>">  
<center><b>글수정</b>
<br>
<form method="post" name="writeform" action="updatePro.jsp" enctype="multipart/form-data">
<input type="hidden" name="num" value="<%=num %>">
<input type="hidden" name="page" value="<%=nowpage %>">
<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input autofocus type="text" size="10" maxlength="10" 
       id="writer" name="writer" value="<%=board.getWriter()%>"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">    
       <input type="text" size="40" maxlength="50" 
       id="subject" name="subject" value="<%=board.getSubject()%>"></td>	
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" 
       id="email" name="email" value="<%=board.getEmail()%>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">파일첨부</td>
    <td  width="330">
       <input type="file" size="40"  name="upload" >
       <%=board.getUpload() %>
     </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"><%=board.getContent() %></textarea> 
     </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
	<td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  		<input type="submit" value="글수정" >  
  		<input type="reset" value="다시작성">
  		<input type="button" value="목록보기" OnClick="location.href='list.jsp?page=<%=nowpage%>'">
	</td>
</tr>
</table>    
</form>      
</body>
</html>

- writeForm.jsp 내용을 복붙하고 수정

- 앞에서 get 방식으로 넘어온 num 과 page 값을 받아서 변수에 저장

- 수정폼에 정보를 뿌려줘야하므로 DB 연동을 위해 DAO 객체 생성

- 상세정보를 구해오는 역할만을 하는 getContent() 메소드 호출

+ 조회수 1 증가 + 상세 정보 구하기 메소드는 updateContent() 였다

- 구해온 상세정보를 value 속성 등으로 뿌려주고 있다, 첨부파일은 첨부파일 내용을 옆에 출력하고 있음

- list.jsp 로 이동하는 경우에는 ?page=<%=nowpage%> 를 붙이므로서 원래 페이지로 돌아가도록 함

- 첨부파일도 수정할 수 있도록하기위해 enctype="multipart/form-data" 가 form 태그 안에 있어야한다

- 위에서 받은 name 과 page 를 form 태그에서 hidden 객체로 updatePro.jsp 로 전달한다

- num 과 page 는 list.jsp -> content.jsp -> updateForm.jsp -> updatePro.jsp 로 3번 넘어가고 있다

 

- DAO 클래스에서 getContent() 메소드 작성

 

첨부파일 수정시  주의

- 사용자가 첨부파일을 수정할 수도, 수정하지 않을 수도 있다

- 수정을 하면 update 문을 수행할때 첨부파일명이 변경된다

- 수정을 하지 않으면 null 값이 들어가면서 테이블의 upload 컬럼에 있는 기존의 첨부파일명이 지워진다

- 이 문제를 첨부파일을 수정하는 경우, 수정하지 않는경우로 updatePro.jsp 에서 나중에 처리해야한다

 

DAO 클래스 상세 정보 구하기 메소드 작성

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 getContent() 메소드 부분 코드만

	// 수정폼 : 상세 정보 구하기
	public BoardDataBean getContent(int num) {
		
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from upload where num = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				board.setUpload(rs.getString("upload"));
			}
		} 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 board;
	}

- 1개 데이터 상세정보를 구해오므로 리턴 자료형은 DTO, 매개변수는 글번호를 받아야하므로 자료형이 int

- updateContent() 메소드를 복붙해서 조회수 증가 부분만 지우기

 

- 목록페이지에서 글을 선택해서 상세페이지로 오고 거기서 '글수정' 을 눌렀을때 나오는 수정폼 확인

 

- '글수정' 을 누르면 updatePro.jsp 에서 사용자가 입력한 비밀번호가 맞는 비밀번호인지 확인 후 수정을 실행한다

- updatePro.jsp 파일을 생성 및 작성하자

- updatePro.jsp

<%@page import="upload.BoardDBBean"%>
<%@page import="upload.BoardDataBean"%>
<%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
<%@page import="com.oreilly.servlet.MultipartRequest"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// 업로드할 디렉토리 위치 구하기
	String path = request.getRealPath("upload");
	System.out.println("path:" + path);
	
	// 첨부파일의 크기 설정(단위 : Byte) : 1MB
	int size = 1024 * 1024;
	
	// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 업로드가 수행된다.
	MultipartRequest multi = 
		new MultipartRequest(request,
				path,	// 업로드 디렉토리 위치
				size,	// 첨부파일의 크기 : 1MB
				"utf-8",	// 인코딩 타입 설정
				new DefaultFileRenamePolicy());	// 중복 문제 해결
	
	int num = Integer.parseInt(multi.getParameter("num"));
	String nowpage = multi.getParameter("page");
				
	String writer = multi.getParameter("writer");
	String subject = multi.getParameter("subject");
	String email = multi.getParameter("email");
	String content = multi.getParameter("content");
	String passwd = multi.getParameter("passwd");
	
	// 오리지널 파일명 : 클라이언트가 업로드한 파일명
	String upload0 = multi.getOriginalFileName("upload");
	
	// 실제 서버에 저장된 파일명
	String upload = multi.getFilesystemName("upload");
	
	BoardDataBean board = new BoardDataBean();
	board.setNum(num);
	board.setWriter(writer);
	board.setEmail(email);
	board.setSubject(subject);
	board.setContent(content);
	board.setPasswd(passwd);
	board.setIp(request.getRemoteAddr());	// 클라이언트의 IP 주소
//	board.setUpload(upload);	// 첨부파일을 수정하지 않으면 null값이 저장된다.
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(num); // DB에서 상세정보 가져옴
	
	if(upload != null) {	// 첨부 파일을 수정한 경우
		board.setUpload(upload);
	} else {	// 첨부 파일을 수정하지 않은 경우
		board.setUpload(old.getUpload());
	}

	// 비번 비교
	if(old.getPasswd().equals(passwd)){	// 비번 일치시
		int result = dao.update(board);	// update SQL문 실행
		
		if(result == 1) {
%>
		<script>
			alert("글 수정 성공");
			location.href="list.jsp?page=<%=nowpage%>";
		</script>
<%		}
	} else {%>
		<script>
			alert("비번이 일치하지 않습니다.");
			history.go(-1);
		</script>
<%	}%>

- writePro.jsp 의 내용을 복붙 후 수정

- writeForm.jsp 에서 hidden 으로 넘어온 글 번호 num 과 페이지 번호 page 값을 multi.getParamter() 받아서 변수에 저장

- hidden 으로 넘어온 이 값들도 request 객체가 아닌 MultipartRequest 객체 multi.getParameter() 로 값을 전달받아야한다

- useBean action tag 가 사용불가능하므로 DTO 객체를 직접 만들어서 setter 메소드로 값을 설정함

- 그리고 hidden 으로 넘어온 num 을 DTO 객체 board 에 세팅

- 수정폼에서 넘어온 비밀번호와 DB에서 가져온 비밀번호가 일치해야한다

- 비번 일치시 DAO 의 update() 를 호출하여 update sql 을 실행함

+ 페이지번호를 3번째 가져왔으므로 수정 성공시 list.jsp 로 돌아갈때 원래 페이지로 돌아갈 수 있다

 

첨부파일 수정 처리 (updatePro.jsp 부분)

	BoardDataBean old = dao.getContent(num); // DB에서 상세정보 가져옴
	
	if(upload != null) {	// 첨부 파일을 수정한 경우
		board.setUpload(upload);
	} else {	// 첨부 파일을 수정하지 않은 경우
		board.setUpload(old.getUpload());
	}

- 두 경우를 나눠서 처리해야함

- updateForm.jsp 에서 넘어온 upload 가 null 이면 첨부파일을 선택하지 않은 경우이다

1. 첨부파일을 선택한 경우

- 수정폼에서 첨부파일을 선택하면 문제 없이 setUpload(upload) 되어 수정됨

2. 첨부파일을 선택하지 않은 경우

- 수정폼에서 첨부파일을 선택하지 않으면 upload 가 null 이 되어 setUpload(upload) 시 null 값이 들어감

- 수정폼에서 첨부파일을 선택하지 않으면 원래 파일명 그대로 들어가도록 해야함

- DB 에서 가져온 상세정보인 DTO 객체 old 에서 old.getUpload() 해서 원래의 파일명을 가져와서 객체 board 에 세팅

 

- DAO 클래스에서 실제로 수정을 해주는 update() 메소드를 작성하자

 

DAO 클래스 글 수정 + 첨부파일 수정 메소드 작성

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 update() 부분 코드만

	// 글 수정
	public int update(BoardDataBean board) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update upload set writer=?,email=?,subject=?,";
			sql += "content=?,ip=?,upload=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setString(5, board.getIp());
			pstmt.setString(6, board.getUpload());
			pstmt.setInt(7, board.getNum());
			
			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;
	}

- updatePro.jsp 에서 첨부파일이 없는경우 기존 첨부파일명을 board 객체에 넣었으므로 여기선 자유롭게 board.getUpload() 로 값을 세팅하면 된다

- ip 주소는 수정할 필요 없지만 updatePro.jsp 에서 ip 주소도 새로 구해서 board 객체에 넣었으므로 새로 세팅

 

- 수정 기능을 확인하기 위해 목록페이지->상세페이지->수정폼에서 수정해보자

- 첨부파일을 수정하지 않았을때

- 첨부파일을 수정하지 않으면 기존 파일이 그대로 나옴

 

 첨부파일을 수정했을때

- 첨부파일이 수정되었다

 

- 다음은 삭제 기능을 구현하자

 

자료실 게시판 : 글 삭제 & 첨부파일 삭제

- 상세 페이지인 content.jsp 파일에서 '글삭제' 버튼 클릭시 deleteForm.jsp 파일로 넘어간다

- 글번호인 num 과 페이지번호인 page 값이 전달된다

- list.jsp -> content.jsp -> deleteForm.jsp 로 넘어온 값들이다

 

- 먼저 삭제폼 deleteForm.jsp 파일 생성 후 작성

-deleteForm.jsp

<%@page import="upload.BoardDataBean"%>
<%@page import="upload.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
%>

<html>
<head>
	<title>게시판</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="check.js"></script>
</head>   

<body bgcolor="<%=bodyback_c%>">  
<center><b>글삭제</b>
<br>
<form method="post" name="writeform" action="deletePro.jsp">
<input type="hidden" name="num" value="<%=num %>">
<input type="hidden" name="page" value="<%=nowpage %>">
<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
	<td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  		<input type="submit" value="글삭제" >  
  		<input type="reset" value="다시작성">
  		<input type="button" value="목록보기" OnClick="location.href='list.jsp?page=<%=nowpage%>'">
	</td>
</tr>
</table>    
</form>      
</body>
</html>

- updateForm.jsp 파일 내용 복붙 후 수정, 비밀번호 입력양식 외 입력양식 삭제

- deleteForm.jsp 에서는 DB 연동이 필요없으므로 updateForm.jsp의 DB 연동 코드 삭제

- 삭제폼에서는 첨부파일이 전달되지 않기때문에 enctype="multipart/form-data" 를 지워야한다!

- 지우지 않으면 다른 값들 전달에 문제가 생기므로 반드시 지워야함

- num 과 page 값은 3번째 deletePro.jsp 로 또다시 넘어간다

- 폼을 통해서 num, page 가 hidden 으로 넘어가고, 사용자가 입력한 비번 passwd 가 deletePro.jsp로 넘어간다, 총 3개가 넘어감

 

- 다음으로는 deletePro.jsp 파일을 생성 및 작성

- deletePro.jsp

<%@page import="upload.BoardDataBean"%>
<%@page import="upload.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="upload.BoardDataBean"/>
<jsp:setProperty property="*" name ="board"/>

<%
	String nowpage = request.getParameter("page");

	String path = request.getRealPath("upload");
	System.out.println("path:" + path);
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시
		int result = dao.delete(old, path);	// 글 삭제, 첨부파일 삭제
%>
		<script>
			alert("글 삭제 성공");
			location.href="list.jsp?page<%=nowpage%>";
		</script>
		
<%	} else {	// 비번 불일치시 %>
		<script>
			alert("비번이 일치하지 않습니다");
			history.go(-1);
		</script>
<%	}%>

- 한글값이 post 로 넘어올 수 있으므로 한글값 인코딩

- 현재는 첨부파일이 넘어오지 않으므로 useBean action tag, setProperty action tag, request.getParameter() 사용 가능

- 넘어온 값 3개 중에 page 는 DTO 프로퍼티에 없으므로 따로 변수를 만들어서 값을 저장

- 글을 삭제하면 첨부파일도 같이 지워야한다, 지우지 않으면 서버측에 계속 저장되어 공간 차지

- 비번을 비교하기 위해 이전에 구현한 DAO의 getContent() 메소드 호출해서 상세정보를 DTO 객체 old 에 저장

- 해당 글의 상세정보를 저장하는 객체 old를 delete() 메소드의 첫번째 매개변수로 넘긴다

- 글번호만 필요하다면 board 객체를 전달해도 되지만 첨부파일명 또한 구해야할 일이 있으므로 객체 old 를 넘김

 

첨부파일 지우기

- JAVA의 File 클래스 관련 내용을 써야함

- 디렉토리 'upload' 의 절대 경로값을 request.getRealPath() 로 구해서 path 변수에 저장한다

-  path 변수를 DAO의 delete() 메소드의 두번째 매개변수로 넘긴다

 

- 이제 DAO 클래스에서 글 삭제 와 첨부파일 삭제 메소드인 delete() 메소드를 작성하자

 

DAO 클래스 글 삭제 & 첨부파일 삭제 메소드 작성

- 전체 코드는 가장 마지막에 한번만

- DAO 클래스 BoardDBBean.java 에서 추가한 delete() 부분 코드만

	// 글 삭제
	public int delete(BoardDataBean board, String path) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "delete from upload where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board.getNum());
			result = pstmt.executeUpdate();
			
			if(board.getUpload() != null) {	// 첨부 파일이 있으면
				File file = new File(path);
				
				// upload 디렉토리의 모든 파일을 읽어온다.
				File[] f = file.listFiles();
				
				for(int i=0; i<f.length; i++) {
					
					// upload 디렉토리에 저장된 파일 중에서 db에 저장된 파일을 삭제한다.
					if(f[i].getName().equals(board.getUpload())) {
						f[i].delete();	// 첨부 파일 삭제
					}
				}
			}
		} 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;
	}

- 매개변수가 2개이다, 첫번째로는 삭제할 글의 정보인 DTO 객체 board, 두번째로는 첨부파일 저장되는 실제 경로인 path

- 삭제폼에서 hidden 으로 넘어온 num 값을 board.getNum() 으로 가져와서 해당 글을 삭제

- 글 삭제 후 첨부파일을 삭제해야 한다.

 

첨부파일 삭제 (BoardDBBean.java 의 delete() 중 부분)

			if(board.getUpload() != null) {	// 첨부 파일이 있으면
				File file = new File(path);
				
				// upload 디렉토리의 모든 파일을 읽어온다.
				File[] f = file.listFiles();
				
				for(int i=0; i<f.length; i++) {
					
					// upload 디렉토리에 저장된 파일 중에서 db에 저장된 파일을 삭제한다.
					if(f[i].getName().equals(board.getUpload())) {
						f[i].delete();	// 첨부 파일 삭제
					}
				}
* 관련 내용 수업 : https://laker99.tistory.com/48
* 관련 내용 과제 : https://laker99.tistory.com/39 (검색어 : 1. 디렉토리 삭제)
- 비어있는 폴더는 잘 지워졌지만 비어있지 않은 폴더는 지워지지 않았음
- 그 폴더안의 파일을 File 배열로 받아서, 반복문을 통해 모든 파일을 일일히 지운 후 폴더를 지웠다

- 여기서도 마찬가지로 해당 파일을 지우기 위해 upload 폴더의 모든 파일명을 File 배열로 가져오고, 반복문으로 돌리면서 그 중 DB의 파일명과 일치한다면 파일을 삭제

- upload 디렉토리까지의 경로로 File 객체 file 을 생성함

- 그 디렉토리 안의 모든 파일명을 가져와야한다

- 그러므로 File 객체 file 로 file.listFiles() 메소드를 통해 upload 디렉토리의 모든 파일을 읽어온다

- deletePro.jsp 에서 DB에서 검색한 상세정보를 저장한 객체 old 를 매개변수로 전달하므로 old 객체로부터 DB에 저장된 파일명을 구할 수 있다

- upload 디렉토리의 파일명 중 DB에 저장된 파일명과 일치한다면 해당 파일삭제

 

- 삭제 기능이 잘 구현되었는지 확인하기 위해 목록페이지->상세페이지->삭제폼 에서 비밀번호를 입력해서 삭제 해보자

- 기존에 있던 첨부파일

- 글이 삭제되었음을 알 수 있다

 

- 콘솔창 확인시

- deletePro.jsp 에서 첨부파일이 저장되는 upload 디렉토리의 실제 경로 구하고 출력한다

- 그 경로로 탐색기에서 확인하면

- 첨부파일 (해파리 사진) 또한 삭제되었음을 알 수 있다

 

첨부파일 수정 이전 파일 문제점

- 첨부파일을 코알라 사진에서 해파리 사진으로 수정하였다

- 해파리 사진은 글을 삭제할때 같이 삭제되었지만 수정 이전 첨부파일인 코알라 사진은 그대로 upload 폴더에 남아있음

- 이전 첨부파일을 지워주는 코드가 없기때문이다.

- 코알라 사진 또한 지우려면 코알라 사진에서 해파리 사진으로 수정할때 코알라 사진을 삭제해야한다

 

자료실 게시판 : 초기 페이지

- 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>
<script>
	location.href = "upload1/list.jsp";
</script>
</body>
</html>

- index.jsp 파일에서 목록페이지 list.jsp 로 바로 가도록 설정

- index.jsp 파일 기준에서 list.jsp 파일은 upload1 폴더 하위에 있으므로 upload1/list.jsp 로 경로 설정


앞으로 학습할 것들

- 이제 Model 1 으로 만들 수 있는 프로그램 끝났다

 

Model 2 로 넘어가기 전에 3가지를 공부

1. Java Servlet

- 흐름을 제어하는 Controller 클래스를 만들때, Java Servlet 클래스로 만드므로 Java Servlet 클래스 만드는법 배우기

2. 표현언어(EL)

- View 페이지에 결과를 출력할때, 기존 표현식 태그 대신 표현언어를 사용하므로 표현언어(EL) 배우기

3. JSTL

- 사용자 정의 표준인 JSTL 배우기

 

Model 2 구조

- 70% 정도가 Java 코드로 만들어진다

- Model, View, Controller 로 분리되어 개발된다

- Cotroller 클래스 :모든 Class 의 진입점 역할, Java Servlet 클래스로 만들어짐

- Service 클래스 : 실제 요청 처리, 상속 관련

- View 페이지 : EL 이나 JSTL 로 출력

- DAO 클래스에서 sql 문 실행, DAO 클래스는 Model 1과 같다

댓글게시판 : 댓글 작성 기능 (이어서)

현재 작성된 글과 변수 상태

- 목록페이지에서 목록을 보여줄때 ref 컬럼으로 내림차순 정렬, re_step 컬럼으로 오름차순 정렬

- ref 컬럼으로 정렬시 최근글이 위로 간다, 그리고 부모글과 자식글(댓글)은 ref 값이 같다

- 그러므로 re_step 컬럼으로 오름차순 정렬을 한다

 

만약

- '다섯번째 원문'을 선택하고 '네번째 댓글' 을 달면 다섯번째 원문에 달린 댓글과 대댓글들의 re_step 값이 모두 1씩 증가

- 'Re.두번째 댓글' 을 선택하고 'Re.3대댓글'을 달면 부모의 re_step 인 2 보다 큰 re_step 값을 가진 3,4,5 를 re_step 으로 가진 댓글의 re_step 을 1씩 증가하고 자신이 3이 됨

 

reply() 메소드 SQL문 정리

- 원문이 부모일때, 댓글이 부모일떄를 나눠서 생각해야한다

 

 

* 주의 : 작성 순서를 자세히 보기

* 먼저 달린 댓글 = re_step 값이 크다, 즉 부모보다 큰 re_step 값을 가진 댓글은 부모보다 먼저 달린 댓글임

 

1. 원문이 부모일때

1-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 원문글에 달려있던 댓글들 지칭

- 조건 b. 're_step > 부모의 re_step' 의미 = 부모의 re_step 은 0 이므로 그 원문의 모든 댓글 지칭

- 조건 a & b. 그 부모의 모든 댓글을 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

1-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 1 로 설정

- re_step 은 부모보다 +1 을 해서 1 로 설정, 기존의 댓글들이 모두 update 문에 의해 re_step값이 +1 되어서 2부터 시작하므로 새 댓글의 re_step 값을 1로 설정할 수 있다

 

2. 원문이 댓글일때

2-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 댓글의 원문 + 그 원문의 모든 댓글들 + 그 원문의 대댓글들 (즉 그림에서 빨간색으로 묶은 모든 글)

- 조건 b. 're_step > 부모의 re_step' 의미 = 그 부모 댓글과 형제인 댓글 중 부모 댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들

- 조건 a & b. 그 부모댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

2-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 2 로 설정

- re_step 은 부모보다 +1 을 해서 설정, 그러므로 부모 바로 밑에 출력된다, 부모댓글보다 먼저 작성되었던 댓글과 그 댓글의 대댓글들이 모두 update 문에 의해 re_step 값이 +1 이 되어서 비어있는 숫자를 가지는 것

+ 그림처럼 정렬하기 위해서 이 작업을 해야한다, 단순 작성 최신순이 아님, re_step 기준으로 오름차순 정렬이다


+ 원문 글 작성양식과 댓글 작성 양식이 따로 되어있어야만 시퀀스를 사용 가능하다
+ 댓글을 작성할때 num, page, 부모글에 대한 3가지 정보 (ref, re_step, re_level) 의 변수값을 넘겼다

- 목록페이지 list.jsp 로 부터 시작해서 값이 계속 넘어가고 있는 것을 URL 창에서 확인 가능


 

댓글게시판 : 글 수정

- 수정 폼부터 만들어야 한다

- 상세페이지 content.jsp 에서 버튼 4개 중 '수정' 버튼에 수정폼인 updateForm.jsp 를 연결

+ content.jsp 전체 코드는 삭제 구현 이후에 올리기

- 목록에서 상세페이지로 넘어갈때 전달되었던 num, nowpage 값을 다시 상세페이지에서 수정폼으로 전달한다

- 이후 updateForm.jsp 를 생성 및 작성하자

 

- updateForm.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>


<html>
<head>
<title>글수정</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<body bgcolor="<%=bodyback_c%>">  
<center><b>글수정</b>
<br>
<form method="post"  action="updatePro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="page" value="<%=nowpage%>">
<input type="hidden" name="ref" value="<%=board.getRef()%>">
<input type="hidden" name="re_step" value="<%=board.getRe_step()%>">
<input type="hidden" name="re_level" value="<%=board.getRe_level()%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" 
       name="writer" value="<%=board.getWriter() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">
       <input type="text" size="40" maxlength="50" id="subject" 
       name="subject" value="<%=board.getSubject() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" 
       name="email" value="<%=board.getEmail() %>"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"><%=board.getContent() %></textarea> </td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글수정" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
    
</form>      
</body>
</html>

- writeForm.jsp 의 내용을 복붙 후 수정

- 먼저 상세페이지 content.jsp 에서 넘어간 num, nowpage 의 값을 받아주는 내용을 추가함

- 글 수정시, 글의 정보를 보여줘야하므로 DB와 연동해서 글 1개의 상세정보를 가져와야한다

- updateContent()와 달리 getContent() 에서는 조회수 증가시키는 작업을 하지 않음

- getContent()로 받아온 상세정보에서 ref, re_step, re_level 값을 getter 메소드로 가져와서 hidden 으로 updatePro.jsp로 넘김

- 즉, 5가지 정보인 num, nowpage, re_step, re_level, re_step 값을 form의 hidden 을 통해 updatePro.jsp 로 넘긴다

- 또한 수정 폼에 입력한 5개의 정보들도 form 을 통해 updatePro.jsp 로 넘긴다

- 입력양식에 받아온 상세정보들을 value 속성으로 출력함, 다만 textarea 는 value 속성이 아닌 태그 사이에 작성해야함

- list.jsp 로 넘어가는 링크들에 ?page=<%=nowpage%> 를 추가해서 원래 페이지로 돌아가도록 하기

 

- DAO 클래스로 가서 getContent() 메소드를 추가하자

 

DAO 클래스 글 상세 정보 구하기 메소드 작성

- BoardDBBean.java 전체 코드는 완성 후 올리기 (검색어 : BoardDBBean.java 전체 코드 )

- 추가한 코드인 getContent() 메소드 부분 코드만

	// 수정 폼 : 데이터 1개 추출
	public BoardDataBean getContent(int num) {
		
		BoardDataBean board = new BoardDataBean();	
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} 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 board;
	}

 

- 글 1개에 대한 상세정보를 구해줘야하므로 리턴자료형은 DTO 이다

- 글 번호인 num 으로 상세 정보를 구해와야하므로 매개변수는 int 형

- updateContent() 메소드 안의 내용을 통째로 복붙 후, 조회수 증가시키는 코드 부분만 지우기

- 다음으로 updatePro.jsp 파일을 생성 및 작성하자

- updatePro.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="board" class="reboard.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	board.setIp(request.getRemoteAddr());
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시
		int result = dao.update(board);	// update SQL문 실행
		
		if(result == 1) {
%>
			<script>
				alert("글 수정 성공");
				location.href = "list.jsp?page=<%=nowpage%>";
			</script>
<%		
		}
	} else {	// 비번 불일치시
%>
			<script>
				alert("비번이 일치하지 않습니다.");
				history.go(-1);
			</script>
<%
		
	}
%>

- 폼에서 한글값이 post 방식으로 넘어오므로 한글 인코딩

- useBean, setProperty action tag 를 사용해서 폼에서 넘어온 값을 DTO 객체에 저장

- 이때, 폼에서 ip 주소값은 전달되지 않았고, DTO 프로퍼티중 page 값을 저장할 곳이 없으므로 따로 처리

- DB에서 getContent() 메소드로 비번을 끄집어 내서 수정폼에서 사용자가 입력한 비번과 일치하는지 확인

- getContent() 매개변수에는 글의 번호인 num 이 들어가므로 수정폼에서 넘어온 값들을 저장한 board 객체에서 getNum() 으로 num 값을 꺼내서 매개변수로 넣기

- DB의 비밀번호인 old.getPasswd() 와 사용자가 수정폼에서 입력한 비밀번호인 getPasswd() 가 일치하면 수정 메소드인 update() 호출

 

- DAO 클래스로 가서 update() 메소드를 추가하자

 

DAO 클래스 글 수정 메소드 작성

- BoardDBBean.java 전체 코드는 완성 후 올리기 (검색어 : BoardDBBean.java 전체 코드 )

- 추가한 코드인 update() 메소드 부분 코드만

	// 글 수정
	public int update(BoardDataBean board) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			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;
	}

- update 된 데이터 개수를 반환하기 위해 리턴자료형은 int

- 사용자가 수정폼에서 입력한 값을 전달받아야하므로 매개변수 자료형은 DTO

- 수정폼에서 넘어온 데이터를 저장하는 board 객체에서 값을 가져와서 update 문을 실행

 

- list.jsp 를 실행 후 글 '다섯번째 원문' 을 선택하여 목록페이지->상세페이지-> 수정폼 에서 글 수정을 시도해보자

- 수정 완료

 

댓글게시판 : 글 삭제

- 삭제 폼부터 만들어야 한다

- 상세페이지 content.jsp 에서 버튼 4개 중 '삭제' 버튼에 수정폼인 deleteForm.jsp 를 연결

 

- content.jsp (최종)

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 조회수 1 증가 + 상세 정보 구하기
	BoardDataBean board = dao.updateContent(num);
	
	// 부모글 정보 구하기
	int ref = board.getRef();
	int re_level = board.getRe_level();
	int re_step = board.getRe_step();
	
	SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	String content = board.getContent().replace("\n","<br>");
%>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=400 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>번호</td>
		<td><%=board.getNum() %></td>
		<td>조회수</td>
		<td><%=board.getReadcount() %></td>
	</tr>
	<tr>
		<td>작성자</td>
		<td><%=board.getWriter() %></td>
		<td>작성일</td>
		<td><%=sd.format(board.getReg_date()) %></td>
	</tr>
	<tr>
		<td>제목</td>
		<td colspan=3><%=board.getSubject() %></td>
	</tr>
	<tr>
		<td>내용</td>
		<td colspan=3>
 			<pre><%=board.getContent() %></pre>
			<%=content %>
		</td>
	</tr>
	<tr>
		<td colspan=4 align=center>
			<input type="button" value="댓글"
			onClick="location.href='replyForm.jsp?num=<%=num%>&page=<%=nowpage%>&ref=<%=ref%>&re_level=<%=re_level%>&re_step=<%=re_step%>'">
			
			<input type="button" value="수정"
			onClick="location.href='updateForm.jsp?num=<%=num %>&page=<%=nowpage %>'">
			
			<input type="button" value="삭제"
			onClick="location.href='deleteForm.jsp?num=<%=num %>&page=<%=nowpage %>'">
			
			<input type="button" value="목록" 
			onClick="location.href='list.jsp?page=<%=nowpage%>'">
		</td>
	</tr>
</table>
</body>
</html>

 

- 삭제 폼인 deleteForm.jsp 파일부터 먼저 생성 후 작성

- deleteForm.jsp

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>


<html>
<head>
<title>글삭제</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<body bgcolor="<%=bodyback_c%>">  
<center><b>글삭제</b>
<br>
<form method="post"  action="deletePro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="page" value="<%=nowpage%>">
<input type="hidden" name="ref" value="<%=board.getRef()%>">
<input type="hidden" name="re_step" value="<%=board.getRe_step()%>">
<input type="hidden" name="re_level" value="<%=board.getRe_level()%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page=<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글삭제" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
    
</form>      
</body>
</html>

- updateForm.jsp 파일을 복붙 후 비번 입력 양식 제외 입력 양식 삭제

- DB연동 코드와 ref, re_step, re_level 값을 hidden 으로 넘기는 부분은 지워도 되지만 여기선 놔둠

- form 을 통해 비밀번호가 deletePro.jsp 로 전달됨

 

- deletePro.jsp 생성 및 작성

<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="board" class="reboard.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	board.setIp(request.getRemoteAddr());
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시
		int result = dao.delete(board);	// update SQL문 실행
		
		if(result == 1) {
%>
			<script>
				alert("글 삭제 성공");
				location.href = "list.jsp?page=<%=nowpage%>";
			</script>
<%		
		}
	} else {	// 비번 불일치시
%>
			<script>
				alert("비번이 일치하지 않습니다.");
				history.go(-1);
			</script>
<%
	}
%>

- updatePro.jsp 파일 내용을 복붙 후 수정

- ip 주소값을 DTO 객체에 저장하는 코드는 필요없으므로 삭제

- 비번 일치시 update() 메소드 대신 delete() 메소드 호출

 

삭제시 주의할 점

- 원문을 삭제시 그 아래의 자식 댓글들만 나오게 된다, 보기 좋지 않음

- 일반적으로, 원문을 삭제 시도시 실제로 삭제하진 않고 제목에 삭제되었다는 메세지를 출력하고 링크를 없앤다

- 원문은 re_level 이 0 이거나 re_step 이 0 이므로 원문 구별 가능

 

- DAO 클래스에서 delete() 메소드를 작성하자


DAO 클래스 글 삭제 메소드 작성

- BoardDBBean.java 전체 코드

// DAO (Data Access Object)
package reboard;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean getInstance() {	// 정적 메소드
		return instance;
	}
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	public Connection getConnection() throws Exception {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 원문 글작성
	public int insert(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,board_seq.nextval,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0);		// readcount
			pstmt.setInt(6, 0);		// re_step
			pstmt.setInt(7, 0);		// re_level
			pstmt.setString(8, board.getContent());
			pstmt.setString(9, board.getIp());
			
			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;
	}
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select count(*) from board";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			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;
	}
	
	// 데이터 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += "(select * from board order by ref desc, re_step 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
	
	// 상세 페이지 : 조회수 1 증가 시키고 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// SQL문 실행
			
			sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}
	
	// 댓글 작성
	public int reply(BoardDataBean board) {
		int result = 0;
		
		// 부모글에 대한 정보
		int ref = board.getRef();
		int re_step = board.getRe_step();
		int re_level = board.getRe_level();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
// 1. 원문이 부모인 경우
//		: 원문이 re_step=0 이기 떄문에, 모든 댓글들의 re_step 값이 1 증가 된다.
// 2. 댓글이 부모인 경우
//		: 부모의 re_step 보다 큰 댓글만 re_step 값이 1 증가 된다.
			
			String sql = "update board set re_step=re_step+1 ";
			sql += "where ref=? and re_step > ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, ref);		// 부모의 ref
			pstmt.setInt(2, re_step); 	// 부모의 re_step
			
			pstmt.executeUpdate();		// SQL문 실행
			
			sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,?,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0); 			// readcount
			pstmt.setInt(6, ref); 			// ref
			pstmt.setInt(7, re_step+1); 	// re_step
			pstmt.setInt(8, re_level+1); 	// re_level
			pstmt.setString(9, board.getContent());
			pstmt.setString(10, board.getIp());
			
			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;
	}
	
	// 수정 폼 : 데이터 1개 추출
	public BoardDataBean getContent(int num) {
		
		BoardDataBean board = new BoardDataBean();	
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} 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 board;
	}
	
	// 글 수정
	public int update(BoardDataBean board) {
		
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			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;
	}
	
	// 글 삭제
	public int delete(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		String sql = "";
		
		try {
			con = getConnection();
			
			if(board.getRe_level() == 0) {	// 원문
				sql = "update board set subject=?,content=? where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setString(1, "관리자에 의해서 삭제됨");
				pstmt.setString(2, " ");
				pstmt.setInt(3, board.getNum());
			} else {						// 댓글
				sql = "delete from board where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setInt(1, board.getNum());
			}
			
			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;
	}
}

 

- 추가한 코드인 delete() 메소드 부분 코드만

	// 글 삭제
	public int delete(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		String sql = "";
		
		try {
			con = getConnection();
			
			if(board.getRe_level() == 0) {	// 원문
				sql = "update board set subject=?,content=? where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setString(1, "관리자에 의해서 삭제됨");
				pstmt.setString(2, "");
				pstmt.setInt(3, board.getNum());
			} else {						// 댓글
				sql = "delete from board where num=?";
				pstmt = con.prepareStatement(sql);
				pstmt.setInt(1, board.getNum());
			}
			
			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;
	}

- if문을 이용해서 원문인지 아닌지 re_level 을 가져와서 판별

- 원문인 경우 해당 원문글의 제목을 "관리자에 의해 삭제됨" 으로 수정하고 내용을 ""으로 수정함

- 댓글인 경우 그냥 delete문으로 바로 삭제

 

+ 매개변수에 board 안의 num 변수만 넘겨도 가능하지만 여기선 DTO 객체를 통째로 넘겼다

 

- 댓글을 삭제할때

- list.jsp 를 실행 후 댓글 'Re1.대댓글' 을 선택하여 목록페이지->상세페이지-> 삭제폼 에서 글 삭제를 시도해보자

 

- 원문을 삭제할때

- list.jsp 를 실행 후 댓글 '다섯번째 원문(수정)' 을 선택하여 목록페이지->상세페이지-> 삭제폼 에서 글 삭제를 시도해보자

 

+ 삭제된 글인경우 목록페이지에서 링크를 없애고 싶다면

- 테이블에 특정 컬럼 del 이라는 varchar2 컬럼을 추가하고 insert 당시에 그 컬럼값을 'n' 으로 한다

- 글을 삭제했을때 컬럼값을 'y' 로 설정함

- 그럼 삭제를 한 글인지 판별하는 컬럼이 생긴다

- 그 컬럼을 통해서 삭제된 글은 링크를 달지 않기

 

댓글게시판 : 초기 페이지

- 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>
댓글 게시판

<script>
	location.href = "reboard/list.jsp";
</script>

</body>
</html>

- 가장 먼저 자동으로 실행되는 파일인 index.jsp 파일에 reboard 폴더 안의 list.jsp 가 실행되도록 함

 

- 프로젝트 처음부터 실행하는 방법

- 프로젝트를 클릭후 Run on Server 로 실행시켰을때 index.jsp 부터 자동으로 실행된다

 

delete 원문삭제 가 안됨
update /delete 성공 후 list.jsp 로 돌아가는게 안됨


자료실 게시판 소개

- 첨부파일을 업로드 / 다운로드 가능한게 자료실 기능을 가진 게시판

- 업로드 / 다운로드 하는 방법부터 먼저 학습 후 자료실 기능 게시판 만들 것

 

첨부파일 저장

- 첨부파일은 일반 값과는 달리 테이블에 저장되지 않고 서버측에 지정된 디렉토리 안에 저장된다

- 첨부파일명만 테이블에 저장된다

- 지정한 upload 디렉토리안에 첨부파일이 저장됨

 

첨부파일 업로드 / 다운로드 라이브러리

- 첨부파일 업로드 / 다운로드를 위해서 따로 라이브러리가 필요하다

- 여러 라이브러리 중 cos.jar 라는 라이브러리를 사용해서 첨부파일을 업로드 / 다운로드 할 것

- cos.jar 라이브러리를 프로젝트의 lib 폴더 안에 저장해야한다

 

+ cos 라이브러리 장점

- Model 1, Model 2 에서 많이 사용함

- upload 디렉토리 안에 첨부파일을 저장할때 동일 폴더이므로 파일명 중복문제가 발생시 자동으로 처리해줌

ex) A 가 자료실에 파일명을 kuro.jpg 로 올리고, B도 자료실에 파일명을 kuro.jpg 로 올렸다면 중복 발생

- 자동으로 번호값을 부여해서 중복 문제를 해결함

ex) kuro.jpg, kuro1.jpg, kuro2.jpg 처럼 끝에 숫자를 붙여줌

- 다른 라이브러리는 같은 이름의 파일이 있는지 없는지 검사하며 개발자가 중복 문제를 직접 처리해야함

 

cos 라이브러리 다운

- zip 파일을 다운

- 다운로드 후 압축 풀기

+ doc 폴더 안에는 API 문서가 있다

+ doc폴더 안에서 index 파일 실행시 API 문서를 볼 수 있음

 

 

첨부파일 업로드 / 다운로드

실습 준비 / 환경 구축

- 업로드를 수행하기 위한 예제 파일들이 있는 uploadTest 를 jspproject 프로젝트 로 가져오기

- 이후 WebContent 폴더 하위에 업로드를 하기 위한 폴더인 upload 폴더 생성

- 아까 다운받은 cos-22.05 중 lib 폴더 안의 cos.jar 파일을 이클립스의 jspproject 프로젝트 WEB-INF/lib 폴더 안에 넣기

 

첨부파일 서버로 전송하기

- 반드시 post 방식만 가능하다

- form 태그안에 속성으로 이 코드가 있어야한다

 

- uploadTest 폴더 안의 fileUploadForm.jsp 파일 열기

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<html>
<head>
<title>FileUpload Form</title>
</head>
<body>
<center>
<form action="fileUpload.jsp" method="post" enctype="multipart/form-data">
<table border=1>
	<tr>
		<td colspan=2 align=center><h3>파일 업로드 폼</h3></td>
	<tr>
		<td>올린 사람 : </td><td><input type="text" name="name"></td><br>
	</tr>
	<tr>
		<td>제목 : </td><td><input type="text" name="subject"></td><br>
	</tr>
	<tr>
		<td>파일명1 : </td><td><input type="file" name="fileName1"></td><br>
	</tr>
	<tr>
		<td>파일명2 : </td><td><input type="file" name="fileName2"></td><p/>
	</tr>
	<tr>
		<td colspan=2 align=center><input type="submit" value="전송"></td>
	</tr>
</table>
</form>
</center>
</body>
</html>

- 첨부파일을 서버측으로 전송할 때는 post 방식만 가능하다

- 첨부파일을 서버측으로 전송할 때는 form 태그 안에 반드시 enctype="multipart/form-data" 코드가 있어야한다

- input type=file 로만 해도 버튼이 포함된 이 모양을 만들어준다

+ 첨부파일도 마찬가지로 name 값이 값을 전달하기 위한 변수 역할을 한다

 

- fileUploadForm.jsp 를 실행한 뒤 HTML 시간에 사용했던 사진들을 업로드 하기

 

- 클릭 시 다운로드 된다

 

만약 다시 업로드할때 파일명이 기존 파일명과 중복된다면

- 똑같은 파일명으로 다시 업로드시

- 문제없이 자동으로 잘 처리된다

 

+ 업로드 관련 클래스

- 업로드는 2개의 클래스로 처리된다

- doc 폴더의 index 를 실행하면 실행되는 API 문서에서 클래스 확인 가능

 

- form 태그에서 action 으로 지정된 fileUpload.jsp 파일을 살펴보자

- fileUpload.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="com.oreilly.servlet.MultipartRequest" %>
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %>
<%@ page import="java.util.*" %>
<%

	// 업로드할 디렉토리 위치를 구해옴
	String uploadPath=request.getRealPath("upload");
	System.out.println("path="+uploadPath);	

	int size = 10*1024*1024;	// 첨부파일의 크기(단위:Byte) : 10MB
	String name="";
	String subject="";
	String filename1="";
	String filename2="";
	String origfilename1="";
	String origfilename2="";
	
	try{	// 첨부파일은 MultipartRequest 객체를 생성하면서 업로드 된다.
		MultipartRequest multi=new MultipartRequest(request,
							uploadPath,			// 업로드할 디렉토리 위치
							size, 				// 첨부파일의 크기 : 10MB
							"utf-8",			// 인코딩 타입 설정
				new DefaultFileRenamePolicy());	// 중복 문제 해결

		name=multi.getParameter("name");
		subject=multi.getParameter("subject");
		
		Enumeration files=multi.getFileNames();
		
//	    String file1 = "fileName1";
		String file1 =(String)files.nextElement();
		filename1=multi.getFilesystemName(file1);
		origfilename1= multi.getOriginalFileName(file1);

// 	    String file2 = "fileName2";		
		String file2 =(String)files.nextElement();
		filename2=multi.getFilesystemName(file2);
		origfilename2=multi.getOriginalFileName(file2);
		
	}catch(Exception e){
		e.printStackTrace();
	}
%>
<html>
<body>
<form name="filecheck" action="fileCheck.jsp" method="post">
	<input type="hidden" name="name" value="<%=name%>">
	<input type="hidden" name="subject" value="<%=subject%>">
	<input type="hidden" name="filename1" value="<%=filename1%>">
	<input type="hidden" name="filename2" value="<%=filename2%>">
	<input type="hidden" name="origfilename1" value="<%=origfilename1%>">
	<input type="hidden" name="origfilename2" value="<%=origfilename2%>">
</form>
<a href="#" onclick="javascript:filecheck.submit()">업로드 확인 및 다운로드 페이지 이동</a>
</body>
</html>

- 다른 값과 다르게 첨부파일은 useBean, setProperty, getProperty 로 저장 및 가져오기 불가능함

- 그래서 cos 라이브러리에서 지원되는 2개의 클래스를 사용해서 첨부파일을 처리한다

 

업로드할 디렉토리 위치 구하기 (fileUpload.jsp 부분)

- 파일 업로드 후 upload 폴더로 가보면 파일이 저장되어 있지 않음

- C:\Users\admin\eclipse-workspace\jspproject\WebContent 로 가보면 아무 파일도 없다

	// 업로드할 디렉토리 위치를 구해옴
	String uploadPath=request.getRealPath("upload");
	System.out.println("path="+uploadPath);

- "upload" 라는 폴더를 실제 저장되는 경로로 지정하고 있다

- request.getRealPath() 메소드로 실제 서버에 저장되는 경로값을 구해온다

 

- 실제로 첨부파일이 서버에서 저장되는 위치는 따로 있고, 콘솔창을 보면 확인 가능

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

- 이 위치가 실제 저장 위치이다

- 탐색기에 복붙시

- 같은 파일명인 파일이 들어왔을때는 번호를 뒤에 붙여서 중복문제를 자동으로 해결해 줌

 

cos 라이브러리 업로드 관련 클래스

- 2개의 클래스를 사용해서 업로드

- com.oreilly.servlet.MultipartRequest 클래스 : 실제 업로드를 수행하는 메소드

- com.oreilly.servlet.multipart.DefaultFileRenamePolicy 클래스 : 중복 문제를 해결하는 메소드

 

- 이후 각 클래스 API 문서 볼 것

 

업로드 하기 (fileUpload.jsp 부분)

	// 첨부파일은 MultipartRequest 객체를 생성하면서 업로드 된다.
		MultipartRequest multi=new MultipartRequest(request,
							uploadPath,	// 업로드할 디렉토리 위치
							size, 		// 첨부파일의 크기 : 10MB
							"utf-8",	// 인코딩 타입 설정
				new DefaultFileRenamePolicy());		// 중복 문제 해결

- cos 라이브러리는 따로 업로드 시켜주는 코드가 없고, MultipartRequest 객체를 생성하면서 자동으로 업로드된다

+ 다른 라이브러리는 따로 업로드 시켜주는 코드가 있다

- 매개변수가 5개인 생성자로 MultipartRequest  객체 생성하면서 첨부파일 업로드가 수행됨

- 아래를 보면서 각 매개변수가 무엇을 의미하는지 보자

 

MultipartRequest  생성자 매개변수 의미

- MultipartRequest 클래스 API 문서

MultipartRequest
(javax.servlet.http.HttpServletRequest request,
java.lang.String saveDirectory,
int maxPostSize,
java.lang.String encoding, 
FileRenamePolicy policy)

- 첫번째 매개변수 : request 객체가 되어야함

- 두번째 매개변수 : 업로드할 디렉토리 위치

- 세번째 매개변수 : 최대크기, 바이트 단위, 현재는 10메가까지 설정되어있다

- 네번째 매개변수 : cos 라이브러리로 전달되는 한글값들은 setCharaterEncoding() 메소드를 사용할 필요 없이 여기서 설정 가능

- 다섯번째 매개변수 : DefaultFileRenamePolicy 객체를 생성함으로서 중복 문제를 해결해준다

+ MultipartRequest 객체 생성시 인코딩 시켰으므로 name값과 subject.값이 넘어오더라도 한글값이 깨지지 않는다

 

폼에서 넘어온 첨부파일 외의 다른 값들은 어떻게 받을까? (fileUpload.jsp 부분)

- 폼에서 넘어온 올린 사람, 제목을 받는 방법

 

- request.getParameter() 처럼 request 객체로 값을 받으려고 시도하면 값이 안받아진다

//	String na = request.getParameter("name");
//	out.println("name : " + na);	// name : null (값 전달이 안됨)

 

- 해결 방법 : request 객체가 아닌 생성했던 MultipartRequest 객체인 multi.getParameter() 메소드로 값을 받을 수 있다

		name=multi.getParameter("name");
		subject=multi.getParameter("subject");

 

파일명 구하기 (fileUpload.jsp 부분)

- 사용자가 폼에서 업로드한 첨부파일의 파일명을 구해야한다

- 파일의 name 값을 알지 못하는 경우와 아는 경우로 나뉜다

1. 파일의 name 값을 알지 못하는 경우

- 파일의 name 값인 "fileName1", "fileName2" 를 알지 못하는 경우

- 열거형 Enumeration 에 multi.getFileNames() 로 구해온 이름들을 저장하고 하나씩 가져오기

- multi.getFileNames() 메소드 : input type=file 로 된 양식의 name 값들을 가져옴

		// 열거형 : fileName1, fileName2
		Enumeration files=multi.getFileNames();
		
//	    String file1 = "fileName1";
		String file1 =(String)files.nextElement();
		// 실제 서버에 저장된 파일명 : Koala1.jpg
		filename1=multi.getFilesystemName(file1);
		// 클라이언트가 업로드한 파일명 : Koala.jpg
		origfilename1= multi.getOriginalFileName(file1);

// 	    String file2 = "fileName2";		
		String file2 =(String)files.nextElement();
		filename2=multi.getFilesystemName(file2);
		origfilename2=multi.getOriginalFileName(file2);

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

- getFilesystemName() : 실제 서버에 저장된 파일명 구하기 ex) Koala1.jpg

- getOriginalFileName() : 사용자가 실제 선택한 파일명, 클라이언트가 업로드한 파일명 구하기 ex) Koala.jpg

- 중복문제가 없을땐 두가지가 같은 이름, 중복문제가 생겼을때 번호를 붙여 서버에 저장하면 두 이름이 달라짐

 

2. 파일의 name 값을 아는 경우

- 열거형을 사용할 필요 없이 바로 multi.getFilesystemName() 과 getOriginalFileName() 으로 파일명 구하기

 

- MultipartRequest 클래스 지원 메소드

- 지원되는 메소드가 MultipartRequest 안에 있으면 그걸 사용

- 지원되는 메소드가 없으면 request 객체 사용하면 됨 

 

- 이후 폼을 통해서 값을 전달함

 

값을 fileCheck.jsp 파일로 전달 (fileUpload.jsp 부분)

<body>
<form name="filecheck" action="fileCheck.jsp" method="post">
	<input type="hidden" name="name" value="<%=name%>">
	<input type="hidden" name="subject" value="<%=subject%>">
	<input type="hidden" name="filename1" value="<%=filename1%>">
	<input type="hidden" name="filename2" value="<%=filename2%>">
	<input type="hidden" name="origfilename1" value="<%=origfilename1%>">
	<input type="hidden" name="origfilename2" value="<%=origfilename2%>">
</form>
<a href="#" onclick="javascript:filecheck.submit()">업로드 확인 및 다운로드 페이지 이동</a>
</body>

- hidden 을 사용해서 각 파일의 파일명 2개씩과 name, subject 등 6개의 값을 fileCheck.jsp 로 전송

- 전송버튼이 아니라 링크지만 onClick 에 submit() 메소드를 사용해서 전송가능

 

- 이제 fileCheck.jsp 파일을 살펴보자

- fileCheck.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
	request.setCharacterEncoding("utf-8");
	String name=request.getParameter("name");
	String subject=request.getParameter("subject");
	String filename1=request.getParameter("filename1");
	String filename2=request.getParameter("filename2");
	//아래 두줄 추가
	String origfilename1=request.getParameter("origfilename1");
	String origfilename2=request.getParameter("origfilename2");
	
	
%>
<html>
<head>
<title>파일 업로드 확인 및 다운로드</title>
</head>
<body>
올린 사람 : <%=name %><br>
제목 : <%=subject %><br>
<!-- 파일 명 링크 거는 부분 수정함. -->
파일명1 : <a href="file_down.jsp?file_name=<%=filename1 %>"><%=origfilename1 %></a><br>
파일명2 : <a href="file_down.jsp?file_name=<%=filename2 %>"><%=origfilename2 %></a><p>
</body>
</html>

- hidden 으로 넘어온 값들을 모두 변수에 저장함

+ MultipartRequest 객체 생성시 인코딩 시켰으므로 name값과 subject.값이 계속 넘어가더라도 한글값이 깨지지 않는다

- 파일명을 클릭시 링크를 걸어서 클릭시 file_down.jsp 로 이동해서 파일을 다운하도록 한다

- 클릭시 get 방식으로 file_name 에 실제 서버에 저장된 이름인 filename1, filename2 를 각각 file_down.jsp로 전달함

 

 

- file_down.jsp 파일을 살펴보자

- file_down.jsp

<%@ page contentType="text/html;charset=utf-8" %>
<%@ page import="java.io.File"%>
<%@ page import="java.io.*"%>

<%
   String fileName = request.getParameter("file_name");
   System.out.println("fileName="+fileName);

   String savePath = "upload";
   ServletContext context = getServletContext();
   String sDownloadPath = context.getRealPath(savePath);
   String sFilePath = sDownloadPath + "\\" + fileName;
   System.out.println("sFilePath="+sFilePath);
   
   // jsp에서 OutputStream 사용시 IllegalStateException 해결법 : 2줄 추가
   out.clear();
   out = pageContext.pushBody();
   
   try{
   		byte b[] = new byte[4096];
   		File oFile = new File(sFilePath);

   		FileInputStream in = new FileInputStream(sFilePath);

   		String sMimeType = getServletContext().getMimeType(sFilePath);
   		System.out.println("sMimeType>>>"+sMimeType );

   		// 다운로드 파일의 파일형식(마임타입) 설정
   		// octet-stream은 8비트로 된 일련의 데이터를 뜻합니다. 지정되지 않은 파일 형식을 의미합니다. 
   		if(sMimeType == null) sMimeType = "application/octet-stream";

   		response.setContentType(sMimeType);

   		// 한글 파일명 처리 : 한글 파일명이 깨지는 것을 방지해 준다.
   		String sEncoding = new String(fileName.getBytes("utf-8"),"8859_1");

   		// 다운로드 파일 헤드 지정
   		response.setHeader("Content-Disposition", "attachment; filename= " +  sEncoding);
   
   		// 출력 스트림 생성 : 위의 15, 16라인과 충돌발생함.
   		ServletOutputStream out2 = response.getOutputStream();
   		int numRead;

   		// 바이트 배열b의 0번 부터 numRead번 까지 브라우저로 출력
   		while((numRead = in.read(b, 0, b.length)) != -1) {
    		out2.write(b, 0, numRead);
   		}
   		out2.flush(); 
   		out2.close();
   		in.close();
   		
   }catch(Exception e){
	   e.printStackTrace();
   }

%>

- request.getParameter() 를 통해 get 방식으로 넘어온 file_name 값을 받아서 fileName 변수에 저장함 (실제 저장 이름)

 

절대경로 구하기 (file_down.jsp 부분)

- 다운로드 받을때도 upload 디렉토리까지의 절대경로를 구해와야한다

   String savePath = "upload";
   ServletContext context = getServletContext();
   String sDownloadPath = context.getRealPath(savePath);
   String sFilePath = sDownloadPath + "\\" + fileName;
   System.out.println("sFilePath="+sFilePath);

- getServletContext() 로 context 를 구해오고, 이 context 객체는 request 와 같은 역할을 함

- context.getRealPath() 메소드로 upload 폴더의 진짜 경로를 구해와서 sDownloadPath 에 저장

- sDownloadPath 에 저장된 경로값은 upload 디렉토리까지의 절대경로가 출력됨

sFilePath=C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\jspproject\upload\Penguins3.jpg

 

마임 타입 설정 (file_down.jsp 부분)

   		String sMimeType = getServletContext().getMimeType(sFilePath);
   		System.out.println("sMimeType>>>"+sMimeType );

   		// 다운로드 파일의 파일형식(마임타입) 설정
   		// octet-stream은 8비트로 된 일련의 데이터를 뜻합니다. 지정되지 않은 파일 형식을 의미합니다. 
   		if(sMimeType == null) sMimeType = "application/octet-stream";

   		response.setContentType(sMimeType);

- 다운로드 파일의 파일형식(마임타입) 설정하는 코드

- 콘솔창 출력

 

한글 파일명 처리 (file_down.jsp 부분)

   		// 한글 파일명 처리 : 한글 파일명이 깨지는 것을 방지해 준다.
   		String sEncoding = new String(fileName.getBytes("utf-8"),"8859_1");

- 한글 파일명이 깨지는 것을 방지해주는 코드

 

다운로드 파일 헤드 지정 (file_down.jsp 부분)

   		// 다운로드 파일 헤드 지정
   		response.setHeader("Content-Disposition", "attachment; filename= " +  sEncoding);

- 다운로드 받은 파일의 헤더에 관한 설정

- 인코딩 값등 설정

 

첨부파일을 하나씩 읽어오기 / 다운로드 (file_down.jsp 부분)

   		// 출력 스트림 생성 : 위의 15, 16라인과 충돌발생함.
   		ServletOutputStream out2 = response.getOutputStream();
   		int numRead;

   		// 바이트 배열b의 0번 부터 numRead번 까지 브라우저로 출력
   		while((numRead = in.read(b, 0, b.length)) != -1) {
    		out2.write(b, 0, numRead);
   		}
   		out2.flush(); 
   		out2.close();
   		in.close();

- 첨부파일을 하나씩 읽어오는 작업을 한다

- 읽어올때는 read() 메소드 사용

- 출력스트림을 통해서 write() 함수를 사용시 다운로드가 수행

- 쭉 읽어와서 출력을 시키면 그게 다운로드이다

 


Model 1 DBCP 방식으로 자료실 게시판 만들기

 

자료실 게시판

- 일반게시판에서 첨부파일을 업로드 / 다운로드 기능을 추가한 게시판

 

자료실 게시판 : 주요 기능 소개

1. Connection Pool

2. 액션태그

- <jsp:useBean....>, <jsp:setProperty....>

+ 첨부파일이 있는 경우엔 useBean, setProperty 사용 불가, 없는 경우엔 사용 가능

3. DTO, DAO 클래스

4. 페이징 처리 ( inline View )

5. 첨부파일 업로드 – cos.jar 라이브러리 이용

 

첨부파일 관련 컬럼

1. upload 컬럼

- 테이블에 첨부파일명을 저장해야한다

- 컬럼 upload 을 추가하여, 그 컬럼에 첨부파일명을 저장한다 

- 나머지 컬럼은 일반 게시판과 같다

 

자료실 게시판 : 프로젝트 생성

- 이렇게 구조를 만들기 위해

- upload 라는 프로젝트부터 생성

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

 

자료실 게시판 : 기초 파일 가져오기

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

 

자료실 게시판 : 첨부파일을 저장할 폴더 생성

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

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

 

자료실 게시판 : 몇가지 환경 구축

1) reboard 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

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

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

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

 

자료실 게시판 : 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)          
-->

 

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

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

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

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

 

자료실 게시판 : 테이블 및 시퀀스 생성

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

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

create table upload( 
	num number primary key,
	writer varchar2(20) not null,
	email varchar2(30),
	subject varchar2(50) not null,
	passwd varchar2(20) not null,
	reg_date timestamp not null,
	readcount number default 0,
	content varchar2(2000) not null,
	ip varchar2(20) not null,
	upload varchar2(30) ); -- 첨부파일명

create sequence upload_seq
	start with 1
	increment by 1
	nocache;

 

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

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

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

- upload 컬럼에는 첨부파일 명이 들어간다.

- upload 컬럼을 제외한 나머지 컬럼은 일반 게시판과 동일하다

- 시퀀스는 num 이라는 기본키 컬럼에 들어갈 시퀀스이다

- 테이블과 시퀀스 생성 확인

 

- 생성확인 완료

 

자료실 게시판 : DAO 와 DTO 클래스 만들기

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

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

- 지금은 같은 패키지인 upload 패키지 안에 둘 다 넣자

 

자료실 게시판 : DTO 클래스 작성

- 이걸 가져와서 DTO 클래스에 복붙

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

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

+ java.sql 의 Timestamp import

- getter / setter 메소드 추가

 

- DTO 클래스 BoardDataBean.java 완성 코드

// DTO (Data Transfer Object)

package upload;

import java.sql.Timestamp;

public class BoardDataBean {
	private int num;
	private String writer;
	private String email;
	private String subject;
	private String passwd;
	private Timestamp reg_date;
	private int readcount;
	private String content;
	private String ip;
	private String upload;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public Timestamp getReg_date() {
		return reg_date;
	}
	public void setReg_date(Timestamp reg_date) {
		this.reg_date = reg_date;
	}
	public int getReadcount() {
		return readcount;
	}
	public void setReadcount(int readcount) {
		this.readcount = readcount;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	public String getUpload() {
		return upload;
	}
	public void setUpload(String upload) {
		this.upload = upload;
	}
}

 

자료실 게시판 : 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 클래스 BoardDBBean.java 코드

// DAO (Data Access Object)

package upload;

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

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

 

자료실 게시판 : 파일별 기능 및 구조 설명

- writeForm.jsp 에 첨부파일을 선택하는 양식이 포함되어있다


Model 1 DBCP 방식으로 댓글게시판 만들기

 

댓글 게시판

- 일반게시판에서 댓글을 달 수 있는 기능 추가

 

댓글게시판 : 주요 기능 소개

1. Connection Pool

2. 액션태그

3. DTO, DAO 클래스

4. 페이징 처리 ( inline View )

5. 댓글 처리

- 댓글, 대댓글이 있다

- 테이블 1개로 처리한다, 여러개로 처리하는 방법도 있다

- 테이블쪽에 댓글을 처리하기 위한 새로운 컬럼 3개가 더 들어간다

 

댓글 처리 컬럼 3가지

1. ref

- 관련글

- 부모글과 답글을 그룹으로 묶어주는 역할

- 부모글과 답글은 같은 ref 값을 가진다

- 원문 : num 컬럼과 ref 값이 같은 값을 가진다

- 답글 : 부모글의 ref 값과 같은 값을 가진다

* 가장 바깥쪽 부모글 = 원문

 

+ num 컬럼은 primary key 이고 sequence 로 값이 들어가는 컬럼이다

+ 원문의 num 과 ref 값은 sequence 로 값이 들어가며 같은 값이다

+ 답글에는 부모글과 같은 ref 값을 가지도록 insert

- 답글은 num 과 ref 값이 같은값이 아님, 원문글의 num 과 ref 값이 같은 것임

 

2. re_level

- 답글의 깊이를 저장

- 원문 : 0

- 1단계 답글 : 1

- 2단계 답글 : 2

 

3. re_step

- 답글의 출력 순서를 저장하여 제어

- 원문 : 0 으로 고정

- 답글 : 오름차순 정렬되어서 출력된다, 새로운 답글이 추가될때마다, re_step 값은 계속 변화한다

- 목록페이지에서 글을 정렬하여 처리할때 ref 컬럼 기준으로 내림차순 정렬한다

- 그럼 최근글이 위로가는데 부모글과 답글들은 ref 값이 동일하기때문에 1번 더 정렬조건을 줘야함

- 그때 사용하는 것이 re_step

 

댓글게시판 : 프로젝트 생성

- 이렇게 구조를 만들기 위해

+ 원문 작성 양식과 댓글 작성 양식이 따로 있다 writeForm 과 replyForm

- reboard 라는 프로젝트부터 생성

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

 

댓글게시판 : 기초 파일 가져오기

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

 

댓글게시판 : 몇가지 환경 구축

1) reboard 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

- 커넥션 풀의 환경설정 파일이다

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

 

댓글게시판 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

- context.xml

- dbcpAPITest.jsp

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

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

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

 

댓글게시판 : 테이블 및 시퀀스 생성

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

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

create table board( 
	num number primary key,
	writer varchar2(20) not null,
	email varchar2(30),
	subject varchar2(50) not null,
	passwd varchar2(20) not null,
	reg_date timestamp not null,
	readcount number default 0,
	ref number not null,
	re_step number not null,
	re_level number not null,
	content varchar2(2000) not null,
	ip varchar2(20) not null );

create sequence board_seq
	start with 1
	increment by 1
	nocache;

 

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

 

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

 

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

- 시퀀스는 num 이라는 기본키 컬럼에 들어갈 시퀀스이다

- 테이블과 시퀀스 생성 확인

- 생성확인 완료

 

댓글게시판 : DAO 와 DTO 클래스 만들기

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

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

- 지금은 같은 패키지인 reboard 패키지 안에 둘 다 넣자

 

댓글게시판 : DTO 클래스 작성

- 이걸 가져와서 DTO 클래스에 복붙

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

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

+ java.sql 의 Timestamp import

- getter / setter 메소드 추가

 

- DTO 클래스 BoardDataBean.java 완성 코드

// DTO (Data Transfer Object)
package reboard;

import java.sql.Timestamp;

public class BoardDataBean {
	private int num;	// 프로퍼티(property)
	private String writer;
	private String email;
	private String subject;
	private String passwd;
	private Timestamp reg_date;
	private int readcount;
	private int ref;
	private int re_step;
	private int re_level;
	private String content ;
	private String ip;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public Timestamp getReg_date() {
		return reg_date;
	}
	public void setReg_date(Timestamp reg_date) {
		this.reg_date = reg_date;
	}
	public int getReadcount() {
		return readcount;
	}
	public void setReadcount(int readcount) {
		this.readcount = readcount;
	}
	public int getRef() {
		return ref;
	}
	public void setRef(int ref) {
		this.ref = ref;
	}
	public int getRe_step() {
		return re_step;
	}
	public void setRe_step(int re_step) {
		this.re_step = re_step;
	}
	public int getRe_level() {
		return re_level;
	}
	public void setRe_level(int re_level) {
		this.re_level = re_level;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
}

 

댓글게시판 : 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() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

 

4. 그 이후 댓글게시판 CRUD 관련 메소드

- 가장 먼저 원문 작성 메소드를 만들어야한다, 그 후 다양한 메소드들을 DAO에 작성

 

댓글게시판 : 파일별 기능 및 구조 설명

- 만들어야할 파일들 보기

+ check.jsp 는 유효성 검사

- 원문 글 과 댓글 작성 기능을 따로 처리

- writeForm.jsp 은 원문 글작성 파일

- replyForm.jsp 는 답글 작성 파일


댓글게시판 : 원문 글 작성 기능

- writeForm.jsp 에서 글 작성시 write.jsp 로 넘어감

- writeForm.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>
<html>
<head>
<title>게시판</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<% 
  int num=0,ref=1,re_step=0,re_level=0;	// 원문
 /*  try{  
    if(request.getParameter("num")!=null){  // 답글
	num=Integer.parseInt(request.getParameter("num"));
	ref=Integer.parseInt(request.getParameter("ref"));
	re_step=Integer.parseInt(request.getParameter("re_step"));
	re_level=Integer.parseInt(request.getParameter("re_level"));
	} */
%>
   
<body bgcolor="<%=bodyback_c%>">  
<center><b>글쓰기</b>
<br>
<form method="post"  action="writePro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="ref" value="<%=ref%>">
<input type="hidden" name="re_step" value="<%=re_step%>">
<input type="hidden" name="re_level" value="<%=re_level%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" name="writer"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">
    <%if(request.getParameter("num")==null){%> <!--  원문 -->
       <input type="text" size="40" maxlength="50" id="subject" name="subject"></td>
	<%}else{%>									<!-- 답글 -->
	   <input type="text" size="40" maxlength="50" id="subject" name="subject" value="[답변]"></td>
	<%}%>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" name="email" ></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"></textarea> </td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글쓰기" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp'">
</td></tr></table>    
    
</form>      
</body>
</html>

- 원문 글 작성은 앞서했던 일반게시판의 글 작성 기능과 비슷하다

- 다른 점은 form 태그안에 hidden으로 들어가는 값이 있다

- 변수 선언하는 부분이 추가되었다

- 원문은 ref 는 num 과 같은값, re_step 과 re_level 은 0 으로 고정

- hidden 으로 넘어가는 값들

- DTO 의 프로퍼티들이 int 형이므로 useBean 으로 DTO 객체가 생성될때 자동 초기화 되므로 굳이 hidden 으로 전달하지 않아도 된다, 필수는 아니다

- hidden 으로 값이 넘어오지 않더라도 num, readcount, ref, re_step, re_level 은 자동으로 0 으로 초기화됨 

 

- 여기서 입력한 원문 글과 hidden 으로 전달하는 값을 writePro.jsp 로 전송한다

- wirtePro.jsp 생성 후 작성하자

- writePro.jsp

<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="reboard.BoardDataBean"></jsp:useBean>
<jsp:setProperty property="*" name="board"/>

<%
	// 클라이언트의 IP주소 구해오기
	String ip = request.getRemoteAddr();
	board.setIp(ip);
	
	BoardDBBean dao = BoardDBBean.getInstance();
	int result = dao.insert(board);		// 원문 글 작성
	
	if(result == 1) {
%>
		<script>
			alert("글 작성 성공");
			location.href="list.jsp";
		</script>
<%	} else {%>
		<script>
			alert("글 작성 실패");
			history.go(-1);
		</script>
<%	}%>

- useBean action tag 로 DTO 객체 board 생성, board 의 프로퍼티 중 기본자료형은 모두 초기화됨

- 원문 작성 폼에서 넘어온 hidden 을 포함한 여러 값들이 DTO 객체 board 에 저장됨

- hidden 으로 값이 넘어오지 않았더라도 num, readcount, ref, re_step, re_level 은 자동으로 0 으로 초기화됐을 것

- reg_date (날짜시간), ip (아이피) 는 원문 작성 폼에서 넘어오지 않는다

- reg_date 는 sql 문에서 sysdate 를 작성하면 되고, ip 는 여기서 처리해야하므로 클라이언트의 ip 주소를 request.getRemoteAddr()로 구해서 객체 board 의 ip 프로퍼티에 setIp() 로 저장

- 데이터를 안전하게 저장하기 위해 테이블에 저장하기위해 DAO 의 insert() 메소드를 호출할 것

- 매개변수로서 사용자가 폼에서 입력한 정보를 저장하고있는 객체 board 를 전달

- insert 에 성공하면 alert 를 띄우고 목록 페이지인 list.jsp 로 페이지 이동

 

- DAO 클래스에 가서 insert() 메소드를 작성하자

 

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

- 원문 글 작성 기능을 하는 insert() 작성

+ 원문 글 작성과 답글 작성 기능을 따로 구현해야 sequence 를 사용 가능

- BoardDBBean.java

// DAO (Data Access Object)
package reboard;

import java.sql.Connection;
import java.sql.PreparedStatement;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean getInstance() {	// 정적 메소드
		return instance;
	}
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	public Connection getConnection() throws Exception {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 원문 글작성
	public int insert(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,board_seq.nextval,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0);		// readcount
			pstmt.setInt(6, 0);		// re_step
			pstmt.setInt(7, 0);		// re_level
			pstmt.setString(8, board.getContent());
			pstmt.setString(9, board.getIp());
			
			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;
	}
}

- 추가한 부분 코드만

<insert() 메소드 형식>

- 작성할 글을 저장하고 있는 DTO 객체를 매개변수로 받음

- insert 성공시 1 을 반환하기위해 리턴자료형은 int

<SQL문 부분>

- num 값은 원문이든 답글이든 무조건 sequence 로 1씩 증가한 값이 들어가야함

- 원문의 경우에는 ref 값은 num 값과 같은 값이 들어가야하므로 sequence 값을 넣음

* 1개의 SQL 문에서는 sequence 의 nextval 을 2번 쓰더라도 같은 값이 들어간다! 

ex) num 에 1 이 들어가면 ref 도 1 이 들어감, ref 에 2가 들어가는 것이 아님

+ 원문 작성 양식과 댓글 작성 양식을 1개의 양식으로 작성시 sequence 사용불가, 그땐 그룹함수 사용해야함

+ 원문은 re_step 이 0 이고, 그 아래 답글들의 re_step 은 1,2,3,4.. 으로 저장됨

<? 값 세팅 부분>

- board 객체에서 값을 꺼내서 ? 에 세팅

- 원문은 re_level, re_step 은 0 , 이 값은 이미 넘어올 객체에서 0 으로 설정되었으므로 굳이 0 으로 쓸 필요없지만 0 으로 설정했다

- 글 작성시 readcount 도 0 이므로 0 으로 설정

 

- 잘 구현되었는지 writeForm.jsp 를 실행해서 글 작성 시도해보자

- 아직 목록페이지 list.jsp 가 구현되지 않았으므로 오류 발생

- sql 문에서 테이블 생성을 확인 가능

 

+ SQL Developer 에서 totoro 계정에 대한 connection 을 생성해서 연결하자

- 나중에 SQL문을 실행했을때 어떤 값이 들어갔는지와 각종 컬럼의 값을 확인 가능

 

- 다음으로 목록페이지 list.jsp 를 생성후 작성하자


댓글게시판 : 글 목록 / 게시판 목록

- 기본변수 3가지, 파생변수 6가지 필요

- 원문은 그냥 출력, 댓글은 왼쪽에 여백을 만들어서 출력

ex)

 

 

 - list.jsp 생성 후 작성하자

<%@page import="reboard.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 추출할 데이터의 시작번호
	// endRow : 각 page에 추출할 데이터의 끝번호
	// 1 page : startRow=1,  endRow=10
	// 2 page : startRow=11, endRow=20
	// 3 page : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;  
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		list = dao.getList(startRow, endRow);	// 데이터 10개 추출
	}
	System.out.println("list : " + list);
%>

- 일반게시판의 목록 페이지를 구현할때와 비슷하다

https://laker99.tistory.com/117

- 총 데이터 갯수를 구하기 위해 DB 와 연동 필요, DAO 클래스에서 getCount() 메소드 생성 후 작성 (아래에 있음)

- 이후 리스트를 생성 후 DAO 의 getList(startRow, endRow) 를 호출해서 해당 페이지의 데이터 10개만 가져오기

- DAO 클래스에서 getList() 메소드 생성 후 작성 (아래에 있음)

- list.jsp 실행시 count 값이 출력되고, list 안에 있는 DTO 객체 값(데이터가 있는 주소)가 출력됨

- 1개의 글만 작성했으므로 count 는 1 로 출력, list 의 안의 값(주소)은 1개만 출력됨

- 미완성, 이후 getCount() 와 getList() 메소드를 작성 후 다시 list.jsp 로 돌아와서 작성하기


DAO 클래스의 getCount()

- BoardDBBean.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 board";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			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;
	}

 

- list.jsp 실행시 count 값이 출력

- 1개의 글만 작성했으므로 coutn 는 1 로 출력


DAO 클래스의 getList()

- BoardDBBean.java 중 getList() 부분만

	// 데이터 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += "(select * from board order by ref desc, re_step 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}

<getList() 메소드 형식>

- 돌려줄 데이터가 여러개 이므로 List 가 리턴자료형이다

<SQL문>

- 첫번째 서브쿼리는 rownum 컬럼의 별칭을 지정하기위한 쿼리

- 두번째 서브쿼리의 별칭이 board

- 일반 게시판과 달리 댓글 게시판은 num 컬럼이 아닌 ref 컬럼을 기준으로 내림차순 정렬한다

- 원문의 경우에는 num 과 ref 가 같으므로 최근글이 위로 간다

- 부모글과 댓글은 ref 값이 같으므로, 한번 더 정렬해야하므로 re_step 기준 오름차순으로 추가 정렬한다

+ 원문은 re_step 이 0, 댓글은 1,2,3.. 으로 댓글작성 순서대로 re_step 이 됨

<SQL문 실행 후>

- rs 에 저장된 값을 저장하기 위해 DTO 객체 board 를 생성하고 List 에 추가하고 다시 다음 데이터를 board에 저장

- rs 에 들어있는 값을 컬럼명을 통해서 가져온 뒤 board 객체에 setter 메소드로 저장

 

- DTO 객체가 저장한 주솟값이 출력됨

+ 현재 글이 1개이므로 1개의 DTO 객체(글)만 가져왔다, 1개의 주솟값만 출력됨


- 다시 list.jsp 로 돌아와서 돌려받은 데이터를 출력하기

- list.jsp (이어서)

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="reboard.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 추출할 데이터의 시작번호
	// endRow : 각 page에 추출할 데이터의 끝번호
	// 1 page : startRow=1,  endRow=10
	// 2 page : startRow=11, endRow=20
	// 3 page : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;  
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		list = dao.getList(startRow, endRow);	// 데이터 10개 추출
	}
	System.out.println("list : " + list);
	
	if(count == 0) {
%>	
		작성된 글이 없습니다.
<% 	} else { %>
		<a href="writeForm.jsp">글작성</a> 글갯수 : <%=count %>
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>작성자</th>
				<th>작성일</th>
				<th>조회수</th>
				<th>IP주소</th>
			</tr>
<%
			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage - 1) * page_size;
			SimpleDateFormat sd = 
					new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			
			for(int i = 0 ; i < list.size() ; i++) {
				BoardDataBean board = list.get(i);
%>
				<tr>
					<td><%=number-- %></td>
					<td><%=board.getSubject() %></td>
					<td><%=board.getWriter() %></td>
					<td><%=sd.format(board.getReg_date()) %></td>
					<td><%=board.getReadcount() %></td>
					<td><%=board.getIp() %></td>
				</tr>
<%			} // for end %>
		</table>
<%	} %>

- 일반게시판 글 목록 출력과 비슷

- count 가 0 이 아닌경우 돌려받은 데이터를 출력

- list.get(i) 로 for문을 이용해서 list 안의 DTO 객체를 하나씩 가져와서 for문에서 새로만든 DTO 객체에 저장 후 출력

- 제목에 링크를 걸어서 상세페이지로 넘어가도록 링크설정하고 글번호와 페이지번호 값을 가져가기 (list.jsp 부분)

- 원문이 아닌 댓글인 경우에는 왼쪽에 여백을 설정하기 (list.jsp 부분)

- 댓글의 깊이인 re_level 만큼 왼쪽에 여백이 생성됨

ex) 1단계 댓글은 조금, 2단계 댓글은 더 많이 벌어짐

- 페이지 링크 설정 ( 1 누르면 1 페이지로 이동하는 버튼) 은 아래에서 이어서 작성

- 현재는 원문 글 뿐이므로 댓글 제목 앞의 여백 확인 불가능

- 글을 몇개 더 작성하기

- SQL Developer 에서 확인

- 모두 원문이므로 num 과 ref 값이 같다

- 모두 원문이므로 re_level, re_step 은 0 이다

 

 

- 페이지 링크 설정 ( 1 누르면 1 페이지로 이동하는 버튼) 이어서

- list.jsp 에서 페이지 링크 설정 부분만 (추가한 부분만)

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	
	// pageCount : 총 페이지 수
	int pageCount = count / page_size + ((count % page_size == 0 ? 0 : 1));
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21...
	// endPage : 각 블럭의 끝 페이지 번호 : 	10, 20, 30...
	int startPage = ((currentPage - 1) / 10) * 10 + 1;
	int block = 10;	// 1개 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에는 endPage값을 pageCount값으로 수정
	if(endPage > pageCount) {	// 마지막 블럭인 경우
		endPage = pageCount;
	}
%>
	<!-- 1페이지로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
	// 이전 블럭으로 이동
	if(startPage > 10) {// 첫 블럭이 아닌 경우 %>
		<a href="list.jsp?page=<%=startPage-10%>">[이전]</a>
<%	} 

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) {	// 현재 페이지 %>
			[<%=i %>]
<% 		} else {	%>
			<a href="list.jsp?page=<%=i%>">[<%=i %>]</a>
<% 		}
	} // for end
	
	// 다음 블럭으로 이동
	if(endPage < pageCount) {	// 마지막 블럭이 아닌 경우
%> 		
		<a href="list.jsp?page=<%=startPage+10%>">[다음]</a>
<%	} %>

	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" 
		style="text-decoration:none"> >> </a>
<%} %>

</center>

- 데이터가 적어서 1 페이지만 나온다

- list.jsp 전체 코드 아래에


list.jsp 전체 코드

 

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="reboard.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 추출할 데이터의 시작번호
	// endRow : 각 page에 추출할 데이터의 끝번호
	// 1 page : startRow=1,  endRow=10
	// 2 page : startRow=11, endRow=20
	// 3 page : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;  
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		list = dao.getList(startRow, endRow);	// 데이터 10개 추출
	}
	System.out.println("list : " + list);
	
	if(count == 0) {
%>	
		작성된 글이 없습니다.
<% 	} else { %>
		<a href="writeForm.jsp">글작성</a> 글갯수 : <%=count %>
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>작성자</th>
				<th>작성일</th>
				<th>조회수</th>
				<th>IP주소</th>
			</tr>
<%
			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage - 1) * page_size;
			SimpleDateFormat sd = 
					new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			
			for(int i = 0 ; i < list.size() ; i++) {
				BoardDataBean board = list.get(i);
%>
				<tr>
					<td><%=number-- %></td>
					<td>
<% 
					// 댓글 제목 앞에 여백 주기
					if(board.getRe_level() > 0) {	// 댓글인 경우
						for(int j = 1; j <= board.getRe_level(); j++) {	%>
							&nbsp;&nbsp;
<%						}
					}
%>
<a href="content.jsp?num=<%=board.getNum()%>&page=<%=currentPage%>">			
					<%=board.getSubject() %>
</a>					
					</td>
					<td><%=board.getWriter() %></td>
					<td><%=sd.format(board.getReg_date()) %></td>
					<td><%=board.getReadcount() %></td>
					<td><%=board.getIp() %></td>
				</tr>
<%			} // for end %>
		</table>
<%	} %>

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	
	// pageCount : 총 페이지 수
	int pageCount = count / page_size + ((count % page_size == 0 ? 0 : 1));
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21...
	// endPage : 각 블럭의 끝 페이지 번호 : 	10, 20, 30...
	int startPage = ((currentPage - 1) / 10) * 10 + 1;
	int block = 10;	// 1개 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에는 endPage값을 pageCount값으로 수정
	if(endPage > pageCount) {	// 마지막 블럭인 경우
		endPage = pageCount;
	}
%>
	<!-- 1페이지로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
	// 이전 블럭으로 이동
	if(startPage > 10) {// 첫 블럭이 아닌 경우 %>
		<a href="list.jsp?page=<%=startPage-10%>">[이전]</a>
<%	} 

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) {	// 현재 페이지 %>
			[<%=i %>]
<% 		} else {	%>
			<a href="list.jsp?page=<%=i%>">[<%=i %>]</a>
<% 		}
	} // for end
	
	// 다음 블럭으로 이동
	if(endPage < pageCount) {	// 마지막 블럭이 아닌 경우
%> 		
		<a href="list.jsp?page=<%=startPage+10%>">[다음]</a>
<%	} %>

	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" 
		style="text-decoration:none"> >> </a>
<%} %>

</center>

댓글게시판 : 글 상세페이지 (상세 정보 구하기)

- 다음으로 상세페이지 content.jsp 를 생성하자

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="reboard.BoardDataBean"%>
<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 조회수 1 증가 + 상세 정보 구하기
	BoardDataBean board = dao.updateContent(num);
	
	// 부모글 정보 구하기
	int ref = board.getRef();
	int re_level = board.getRe_level();
	int re_step = board.getRe_step();
	
	SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	String content = board.getContent().replace("\n","<br>");
%>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=400 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>번호</td>
		<td><%=board.getNum() %></td>
		<td>조회수</td>
		<td><%=board.getReadcount() %></td>
	</tr>
	<tr>
		<td>작성자</td>
		<td><%=board.getWriter() %></td>
		<td>작성일</td>
		<td><%=sd.format(board.getReg_date()) %></td>
	</tr>
	<tr>
		<td>제목</td>
		<td colspan=3><%=board.getSubject() %></td>
	</tr>
	<tr>
		<td>내용</td>
		<td colspan=3>
 			<pre><%=board.getContent() %></pre>
			<%=content %>
		</td>
	</tr>
	<tr>
		<td colspan=4 align=center>
			<input type="button" value="댓글">
			<input type="button" value="수정">
			<input type="button" value="삭제">
			<input type="button" value="목록">
		</td>
	</tr>
</table>
</body>
</html>

- list.jsp 에서 넘어온 글번호 num 과 페이지번호 page 값을 getParameter()로 받기

- setInt() 의 매개변수로 쓰기 위해 num 을 int 형으로 형변환

- 조회수 1 증가 + 상세 정보 구하기 를 하는 DAO 의 updateContent() 메소드 호출

- 다음은 updatateContent() 에서 돌려받은 상세 정보 중 댓글과 관련된 3가지 컬럼값을 따로 변수를 만들어서 저장

- 내용의 줄을 바꾸기 위해 board.getContent() 를 replace() 처리해서  새로 만든 content 변수에 저장

- 그 뒤 테이블에 상세 정보 출력

- 댓글 버튼을 포함한 4개의 버튼 생성

- 댓글 버튼을 누르면 댓글 작성 폼으로 넘어감

- 버튼 처리 부분 설명 아래에서 할 것

 

- 먼저 DAO 클래스에 updateContent() 메소드를 생성하자

- 한개의 정보를 검색하므로 리턴 자료형은 DTO, 글번호가 필요하므로 매개변수로 num 전달


DAO 클래스 조회수 1 증가 & 상세 정보 구하기 메소드작성

- BoardDBBean.java  중 updateContent() 메소드 부분만

	// 상세 페이지 : 조회수 1 증가 시키고 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// SQL문 실행
			
			sql = "select * from board where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setRef(rs.getInt("ref"));
				board.setRe_level(rs.getInt("re_level"));
				board.setRe_step(rs.getInt("re_step"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}

- 한개의 정보를 리턴하므로 리턴 자료형은 DTO, 글 번호로 상세 정보를 구하므로 매개변수는 int 형

- 2번의 SQL문을 실행해야한다

1. 조회수 1 증가 update문

2. 상세 정보 구하기 select문

+ DAO 전체 코드는 마지막에

 

- 이후 list.jsp 에서 글을 클릭 하면 상세 페이지로 들어갈 수 있다


상세페이지 버튼 처리

1. 목록 버튼

			<input type="button" value="목록" 
			onClick="location.href='list.jsp?page=<%=nowpage%>'">

- 목록페이지 list.jsp 로 이동하면서 원래 페이지로 돌아가기 위해 nowpage 값을 사용

+ 쌍따옴표 중첩이 되지 않으므로 " " 안에는 외따옴표 ' ' 를 써준다

 

2. 댓글 버튼

			<input type="button" value="댓글"
			onClick="location.href='replyForm.jsp?num=<%=num%>&page=<%=nowpage%>&ref=<%=ref%>&re_level=<%=re_level%>&re_step=<%=re_step%>'">

- 댓글을 작성할 때, 부모글에 대한 정보(변수) 를 가져가야한다

- 일반게시판에서는 2가지인 num 과 page 만 가져갔지만 댓글게시판에선 5가지 변수를 가져가야함

1. num : 부모글의 번호를 가져야함

2. page : 원래 페이지로 돌아가기 위해 가져야함

3. ref : 부모글과 같은 ref 값을 가져야하므로 부모의 ref 값을 가져가야함

4. re_level : 부모글이 0 이면 댓글은 1 이 증가한 1 이어야하므로 부모의 re_level 값을 가져가야함

5. re_step : 부모글 다음 1 증가 된 값이 들어가야하므로 부모의 re_step 값을 가져가야함

= '댓글' 버튼 클릭시 값이 잘 넘어가는지 확인


댓글게시판 : 댓글 작성 기능

- 이제 댓글 작성 폼인 replyForm.jsp 파일을 생성 후 작성

+ 원문 글 작성 폼과 댓글 작성 폼을 따로 만들고 있다, 그래야 sequence 사용 가능

- replyForm.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>
<html>
<head>
<title>게시판</title>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="check.js"></script>
</head>

<% 
  int num=0,ref=1,re_step=0,re_level=0;	// 원문
  String nowpage = "";
 
   if(request.getParameter("num")!=null){  // 답글
		num=Integer.parseInt(request.getParameter("num"));
		ref=Integer.parseInt(request.getParameter("ref"));
		re_step=Integer.parseInt(request.getParameter("re_step"));
		re_level=Integer.parseInt(request.getParameter("re_level"));
		nowpage = request.getParameter("page");
   }
%>
   
<body bgcolor="<%=bodyback_c%>">  
<center><b>글쓰기</b>
<br>
<form method="post"  action="replyPro.jsp">
<input type="hidden" name="num" value="<%=num%>">
<input type="hidden" name="ref" value="<%=ref%>">
<input type="hidden" name="re_step" value="<%=re_step%>">
<input type="hidden" name="re_level" value="<%=re_level%>">
<input type="hidden" name="page" value="<%=nowpage%>">

<table width="400" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" name="writer"></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">
    <%if(request.getParameter("num")==null){%> <!--  원문 -->
       <input type="text" size="40" maxlength="50" id="subject" name="subject"></td>
	<%}else{%>									<!-- 답글 -->
	   <input type="text" size="40" maxlength="50" id="subject" name="subject" value="Re."></td>
	<%}%>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" name="email" ></td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"></textarea> </td>
  </tr>
  <tr>
    <td  width="70"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글쓰기" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp'">
</td></tr></table>    
    
</form>      
</body>
</html>

- writeForm.jsp 의 내용을 복붙 후 수정

- form의 action 을 replyPro.jsp 로 수정 후 hidden 으로 페이지번호 가져가는 코드 추가

- try 주석 부분을 풀고 이전 페이지에서 넘어온 값을 저장하기

- 전에서 넘어온 값인 num 값이 null 이 아닐때 == '부모글이 있을때', 즉, '댓글이면' 이라는 의미

- hidden 을 포함하여 총 10가지 값이 replyPro.jsp 로 넘어가고 있다

- 또, 댓글의 제목에는 "Re." 를 value 로 설정

- 댓글 작성 폼

 

- 다음으로는 replyPro.jsp 파일을 생성 후 작성

<%@page import="reboard.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="reboard.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	board.setIp(request.getRemoteAddr());
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	int result = dao.reply(board);	// 댓글 작성
	
	if(result == 1) {
%>		
		<script>
			alert("댓글 작성 성공");
			location.href="list.jsp?page=<%=nowpage%>";
		</script>
<%	} else { %>
		<script>
			alert("댓글 작성 실패");
			history.go(-1);
		</script>
<%	}%>

1. replyForm.jsp 에서 post 방식으로 한글값이 넘어오므로 한글값 인코딩

2. useBean, setProperty action tag 로 폼에서 넘어온 값을 DTO 객체 board에 저장

3. ip 주소는 폼에서 넘어오지 않으므로 직접 setIp() 로 저장 

4. 그리고 넘어온 페이지번호는 DTO 프로퍼티에 없으므로 DTO 객체에 저장불가, 따로 다른 변수 nowpage 를 생성해서 저장

5. DAO 의 reply() 메소드로 댓글 작성

- 댓글작성할떄 필요한 값들을 저장한 DTO 객체 board 를 매개변수로 전달

6. 댓글 작성 성공시 목록페이지 list.jsp 로 이동, 원래 페이지로 이동하기 위해 nowpage 값 사용

 

+ DAO 클래스의 댓글 작성 기능을 하는 reply() 메소드

- DAO reply() 메소드가 어렵다

- 2가지 SQL 문을 사용할 것

- 댓글 관련 3가지 컬럼 중 re_step 컬럼 작업이 어려움

- re_step : 원문은 0으로 고정 , 댓글, 대댓글은 고정된 값이 아닌 계속해서 바뀜

- update 문으로 re_step 값을 증가시켜줘야함

 

DAO 클래스 댓글 작성 메소드 작성

- BoardDBBean.java 중 reply() 부분 코드만

	// 댓글 작성
	public int reply(BoardDataBean board) {
		int result = 0;
		
		// 부모글에 대한 정보
		int ref = board.getRef();
		int re_step = board.getRe_step();
		int re_level = board.getRe_level();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
// 1. 원문이 부모인 경우
//		: 원문이 re_step=0 이기 떄문에, 모든 댓글들의 re_step 값이 1 증가 된다.
// 2. 댓글이 부모인 경우
//		: 부모의 re_step 보다 큰 댓글만 re_step 값이 1 증가 된다.
			
			String sql = "update board set re_step=re_step+1 ";
			sql += "where ref=? and re_step > ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, ref);		// 부모의 ref
			pstmt.setInt(2, re_step); 	// 부모의 re_step
			
			pstmt.executeUpdate();		// SQL문 실행
			
			sql = "insert into board values(board_seq.nextval,?,?,?,?,";
			sql += "sysdate,?,?,?,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, 0); 			// readcount
			pstmt.setInt(6, ref); 			// ref
			pstmt.setInt(7, re_step+1); 	// re_step
			pstmt.setInt(8, re_level+1); 	// re_level
			pstmt.setString(9, board.getContent());
			pstmt.setString(10, board.getIp());
			
			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;
	}

1. 객체 board 에 저장되어 있는 부모글에 대한 정보를 구해온다

- hidden 으로 넘어왔던 부모글의 정보들이다

- ref, re_step, re_level 이다

2. SQL 문을 2번 실행, 첫번째로 update 문

- update문으로 re_step 을 1 증가시킨다

- update문을 ref 가 부모의 ref 와 같고 re_step 이 부모의 re_step 보다 클때만 실행하라는 where 문의 조건

3. 두번째로 댓글 삽입 위한 insert
- 원문에서는 ref 자리 ?에 sequence 가 들어갔지만, 댓글에선 ref 자리 ? 에 부모의 ref 가 들어가야함

- ref 자리에는 부모의 ref 값이 들어간다

- re_step 자리에는 부모의 re_step 보다 1 증가한 값이 들어간다

- re_level 자리에는 부모의 re_level 보다 1 증가한 값이 들어간다

 

4. 원문이 부모인 경우와, 댓글이 부모인 경우를 나눠서 생각해야한다

- 그럼 두 경우 update문 where 부분의 의미가 달라진다

- update문에서 re_step 이 1 증가되는건 

 

1) 원문이 부모인 경우 (가장 바깥쪽 글 = 원문)
- 원문이 re_step=0 이기 떄문에, update 문에 의해 새 댓글을 달면 그 원문의 기존에 있던 모든 댓글들의 re_step 값이 1 증가 된다.

- where 문에서 ref 가 부모의 ref 값과 같을때 라는 의미는 '그 원문과 원문의 댓글들 집합 중에서' 라는 의미

- 부모의 re_step 값은 0 이므로 항상 re_step > 0 을 만족하므로 re_step 이 1 증가

- 그리고 원문의 기존 댓글들을 모두 re_step 을 1 증가 시킨 후, 본인의 re_step 값을 부모의 re_step 값보다 1 큰, 즉 1을 삽입
2) 댓글이 부모인 경우
- 부모의 re_step 보다 큰 댓글만 re_step 값이 1 증가 된다.

- 부모보다 큰 re_step 값을 가진 댓글만 re_step 이 증가됨

- 대댓글을 쓸때는 ref=부모의 ref 라는 where문의 첫번째 조건에서, 원문글, 그 글 댓글, 그 글 대댓글을 모두 포함 범위

- 그 다음의 where 조건인 부모의 re_step 보다 큰 값 이라는 의미는,  부모 댓글보다 먼저 달린 모든 댓글과 그 댓글의 대댓글들을 의미

 

* 아래에 정리되어있다

 

- '다섯번째 원문' 에 댓글을 달아보자

- 부모글과 댓글은 ref 값이 7 로 동일하다

- 1단계 댓글은 re_level 이 1

- 첫번째 댓글은 re_step 은 현재 1 이지만, 새로운 댓글을 달면 2로 바뀐다, 이 역할을 update 문이 한다

 

- 댓글을 하나 더 달아보자

- 나중에 단 댓글이 댓글 중 가장 위에 나타나고, re_step 값이 1이 된다

- 첫번째 댓글은 re_step 값이 2가 된다

- 즉 부모가 원문인 경우에는 insert 문에서 re_step 이 부모의 re_step 보다 1 증가한 값이 삽입됨

- 그렇기때문에 ref 기준 내림차순 정렬 다음으로 추가로 re_step 기준 오름차순 정렬할때 정렬되어 부모글 바로 아래에 최근 댓글이 출력됨

 

- 세번째 댓글을 달자

 

- 이제 '두번째 댓글' 에 대댓글을 달아보자

- '1대댓글'을 작성시 update 문으로 '첫번째 댓글' 의 re_step 값이 부모인 '두번째 댓글' 의 re_step 값보다 크므로  '첫번째 댓글' 의 re_step 값을 1 증가시킴

- 그 후 insert 문으로 부모인 '두번째 댓글' 의 re_step 값보다 1 증가한 값인 3을 자신의 re_step 값으로 설정


reply() 메소드 SQL문 정리

- 원문이 부모일때, 댓글이 부모일떄를 나눠서 생각해야한다

* 주의 : 작성 순서를 자세히 보기

* 먼저 달린 댓글 = re_step 값이 크다, 즉 부모보다 큰 re_step 값을 가진 댓글은 부모보다 먼저 달린 댓글임

 

 

1. 원문이 부모일때

1-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 원문글에 달려있던 댓글들 지칭

- 조건 b. 're_step > 부모의 re_step' 의미 = 부모의 re_step 은 0 이므로 그 원문의 모든 댓글 지칭

- 조건 a & b. 그 부모의 모든 댓글을 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

1-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 1 로 설정

- re_step 은 부모보다 +1 을 해서 1 로 설정, 기존의 댓글들이 모두 update 문에 의해 re_step값이 +1 되어서 2부터 시작하므로 새 댓글의 re_step 값을 1로 설정할 수 있다

 

2. 원문이 댓글일때

2-1) update 문

- 조건 a. 'ref= 부모의 ref' 의미 = 기존에 그 부모 댓글의 원문 + 그 원문의 모든 댓글들 + 그 원문의 대댓글들 (즉 그림에서 빨간색으로 묶은 모든 글)

- 조건 b. 're_step > 부모의 re_step' 의미 = 그 부모 댓글과 형제인 댓글 중 부모 댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들

- 조건 a & b. 그 부모댓글보다 먼저 달린 모든 댓글과, 그 댓글의 대댓글들 의미

- 조건 a & b 에 해당하는 데이터의 re_step 값을 1 증가시킴

 

2-2) insert 문

- ref 값은 부모와 똑같이 설정

- re_level 은 부모보다 +1 을 해서 2 로 설정

- re_step 은 부모보다 +1 을 해서 설정, 그러므로 부모 바로 밑에 출력된다, 부모댓글보다 먼저 작성되었던 댓글과 그 댓글의 대댓글들이 모두 update 문에 의해 re_step 값이 +1 이 되어서 비어있는 숫자를 가지는 것

+ 그림처럼 정렬하기 위해서 이 작업을 해야한다, 단순 작성 최신순이 아님, re_step 기준으로 오름차순 정렬이다


복습

일반게시판 테이블 컬럼 num

- 게시판엔 번호가 있다, 번호가 들어가는 컬럼이 주로 primary key 가 된다

+ 회원관리에선 id 를 주로 primary key 가 된다

- primary key 로 설정된 컬럼은 where 조건절에 넣어서 원하는 데이터 1개의 상세정보를 구한다

- num 은 화면에 출력되는 번호와 관련이 없는 내부 번호

+ 화면 출력 번호는 중간에 데이터가 지워지더라도 연속적인 번호

 

일반게시판 테이블 컬럼 passwd

- 글 작성시 비번을 입력하고, 글 수정/삭제 시 비번이 일치해야만 삭제 가능

 

일반게시판 테이블 컬럼 readcount

- 조회수이며, number 타입이다

- 디폴트 0 이므로 값을 설정하지 않았을때 0 이 들어간다

+ 숫자 중 값의 변화가 자주 일어나는 것은 number 타입, 자주 바뀌지 않는 값은 varchar2 타입으로 주로 설정한다

ex) 우편번호, 주민번호는 숫자지만 varchar2


일반게시판 : 글 목록 / 게시판 목록

일반게시판 페이징 처리 변수 1

1. 기본변수 page_size

- 한 화면에 출력될 데이터의 개수

- 현재는 10

 

 

변수 pageNum

- 데이터가 11개가 넘어가면 2 페이지가 만들어지고, 21개가 넘어가면 3 페이지가 만들어진다

- 그 페이지번호에 현재 페이지인 list.jsp 링크를 걸것, 현재 페이지가 다시 시작됨

- 2 를 클릭시 list.jsp?page=2 가 링크가 됨

- 그걸 getParameter() 로 받고 pageNum 에 저장하는 것임

 

2. 기본변수 currentPage

- 변수 pageNum 을 int 형으로 형변환한 변수

- 산술적인 연산을 하기 위해 기본자료형으로 변환한 것이다

- 사용자가 페이지를 선택하지 않았을때 1 이 저장됨

 

pageNum 이 null 일때

- 페이지 값이 전달되지 않은 경우를 의미

ex) 바로 list.jsp 를 실행하거나 로그인 성공후 list.jsp 로 넘어왔을때, 즉 사용자가 어떤 페이지를 볼지 클릭하지 않았을때

- pageNum 이 null 인 경우 pageNum = "1" 로 설정해야 int 형으로 형변환 하는 코드에서 오류가 생기지 않음

- 이 코드가 없으면 500 오류 발생

+ 전체데이터를 내림차순 정렬할 것이므로 가장 최근글이 보이는 페이지가 1 페이지

 

3. 기본변수 count

- 총 데이터 개수를 저장

- DB와 연동하여 count(*) 그룹함수로 값을 구해온다

- 페이지를 계산하기 위해 필요

+ 현재 데이터 개수 : 130

 

+ DAO 클래스의 getCount()

 

파생변수 startRow & 파생변수 endRow

- startRow : 각 page에 출력할 데이터의 시작 번호

- endRow : 각 page에 출력할 데이터의 끝 번호

ex) 사용자가 2 페이지를 누르면 (2-1)*(10) +1  로 인해 startRow은 11

ex) 사용자가 2 페이지를 누르면 (2)*(10) 로 인해 endRow 는 20

- DAO의 getList() 를 호출할떄 매개변수로 들어간다

- 그 후 목록을 잘라주는 역할을 하는 SQL문의 rownum 컬럼 자리에 들어간다

			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) board ) ";
			sql += " where rnum >= ? and rnum <= ?";

- num값 기준 내림차순 이므로 num값이 큰값이 위로 간다

- 전체 데이터를 내림차순해서 사용자가 2 페이지 클릭시 11번 ~ 20번 까지의 글을 보여주는 것

 

- 기본변수 3개와 파생변수 2개를 했다, 파생변수 약 6개 필요, 잠시 메소드 봤다가 이후 이어서 작성할 것


getList() 메소드

호출하는 list.jsp 부분

	List<BoardDataBean> list = null;
	if(count > 0) {
		// 게시판 목록
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list : " + list);
/* 	System.out.println(list.get(1).getNum()); */

호출되는 BoardDBBean.java 부분

	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}

- 여러개의 데이터를 반환하므로 리턴자료형이 리스트

- 한개의 데이터를 반환할때는 리턴자료형이 DTO

- 검색된 데이터를 리스트에 바로 저장하는 것이 아니라 DTO 객체를 만든 후 컬럼단위로 잘라서 세팅한 후 리스트에 DTO 객체를 add() 해준다

- 그리고 모든 데이터가 저장된 리스트를 리턴해준다

 

목록을 자르는 서브쿼리 형태의 인라인뷰 SQL문

호출하는 list.jsp 부분

- 힙메모리 상의 주소가 출력됨

- 즉 DTO 객체의 값이다 (힙메모리상에 있는 데이터의 주솟값)

 

- 이후 list.jsp 에서 돌려받은 리스트에서 하나씩 데이터를 꺼내서 테이블에 출력한다

 

+ list 에서 get() 사용시 다운캐스팅

- 제네릭을 쓰고 있으므로 다운캐스팅시 자료형을 명시할 필요 없다

 


일반게시판 : 글 목록 / 게시판 목록 (이어서)

일반게시판 페이징 처리 변수 2 (이어서)

파생변수 number

- 화면에 출력하기 위한 번호

- 각 페이지에 출력될 시작 번호

- ex) 총 데이터가 130개이고, 사용자가 2페이지를 눌렀을때, 130 - (2 - 1) * 10 = 120 이 나온다

- 후행연산이므로 먼저 출력하고 for문에서 반복될떄마다 하나씩 감소되어 출력됨

 

+ 날짜시간 (list.jsp 부분)

- 포멧을 통해 출력

 


페이지 메뉴 바 설계

- 다음은 아래에 나타나는 페이지를 선택할수있는 메뉴를 만들자

- < 를 누르면 1p로 이동, > 를 누르면 마지막 페이지로 이동

- << 를 누르면 이전 '블럭' 으로 이동, >> 를 누르면 다음 '블럭' 으로 이동

- 한개의 블럭 당 10개의 '페이지' 가 출력되도록 하자

* 1 블럭 = 1 ~ 10 페이지 = 최신 100개 글

* 2 블럭 11 ~ 20 페이지 = 최신 101 ~ 200 글 100개

 

페이지 메뉴 바 코드 (list.jsp 부분)

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	// pageCount : 총 페이지 수
	int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
	// endPage : 각 블럭의 끝 페이지 번호 :    10, 20, 30 ...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
}
%>
</center>

- 표 가운데 나타나게 하기 위해 <center></center> 태그 안에 작성

- 글이 있는 경우만 출력하도록 if(count > 0)

<파생변수 pageCount>

- 총 페이지 수를 의미하는 파생변수 pageCount 를 출력해보면

- 현재 13 페이지가 필요하다

<파생변수 startPage>

 

+ 각 파생변수는 아래에서 자세히 설명

 

파생변수 pageCount

	// pageCount : 총 페이지 수
	int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);

- pageCount : 총 페이지 수

- int형과 int형 연산이므로 나머지가 사라지므로, 나머지가 0 이 아닌경우는 1페이지를 추가해줘야하므로 1 을 더한다

- 나머지가 0 인경우는 더하지 않는다 (0을 더한다)

 

파생변수 startPage & 파생변수 endPage

	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
	// endPage : 각 블럭의 끝 페이지 번호 :    10, 20, 30 ...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;

- startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...

- endPage : 각 블럭의 끝 페이지 번호 : 10, 20, 30 ...

- int형 끼리 연산시 int형이 나오므로, currentPage가 1 이어도 startPage는 1, currentPage가 10 이어도 startPage는 1

ex) 사용자가 2 페이지 클릭시, ((2-1)/10) * 10 + 1 = 1 , 즉 2 페이지가 속한 블럭의 첫번째 페이지 번호는 1 이란 의미

ex) 사용자가 12 페이지 클릭시, ((12-1)/10) * 10 + 1 = 11 , 즉 12 페이지가 속한 블럭의 첫번째 페이지 번호는 11 이란 의미

ex) 사용자가 22 페이지 클릭시, ((22-1)/10) * 10 + 1 = 21 , 즉 22 페이지가 속한 블럭의 첫번째 페이지 번호는 21 이란 의미

- 각 블럭의 시작 페이지 번호가 startPage

- 각 블럭의 끝 페이지 번호가 endPage

 

블럭에 페이지 뿌리고 링크 걸기 (list.jsp 부분)

- 이후 블럭에 페이지 뿌려야한다

- 반복문으로 1 블럭에 1 ~ 10 페이지까지 뿌리고, 2 블럭에 11 ~ 20 페이지까지 뿌리는 작업을 해야한다

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) { %>
			[<%=i %>]
<%		} else {%>
			<a href="list.jsp?page=<%=i %>">[<%=i %>]</a>
<%		}
	}

 

- 현재 보고있는 페이지를 제외한 나머지 9개의 페이지에 링크를 걸어서 list.jsp 로 다시 실행시킨다

ex) 현재 1 페이지이면 1 페이지를 제외한 보이는 9개의 페이지에 링크 걸기

- 사용자가 선택한 페이지 번호값을 get 방식으로 list.jsp로 전송하자

- 이걸 list.jsp 위에서 작성했던 코드인 여기서 자르는 것이다

- 이렇게 currentPage 가 설정되는 것이다

- 즉 현재 페이지 번호에 따라 startRow 와 endRow 가 결정되어 SQL 문에서 해당 페이지의 글들을 가져옴

- list.jsp 안에서 값을 전달하기도 하고, 받기도 하고 있다

[4] 클릭시

 

+ 11 페이지로 이동하려면?

- 그 다음 블럭인 2블럭으로 가야한다, 버튼을 만들어서 다음 블럭으로 이동하는 것을 만들 것

 

+ 문제점

- 1블럭이면 무조건 1 ~ 10페이지, 2블럭이면 무조건 11 ~ 20 페이지를 출력하므로 마지막 페이지에 문제가 생김

- 나중에 해결

 

1p 로 이동 (list.jsp 부분)

	<!-- 1page로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>

- 내가 몇번째 페이지에 있든 상관없이 '<<' 클릭시 1페이지로 이동

- 밑줄을 없애기 위해 CSS 사용

 

마지막 페이지로 이동 (list.jsp 부분)

	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" style="text-decoration:none"> >> </a>

- 클릭시 가장 마지막 페이지로 이동

- 마지막 페이지 번호 = 총 페이지 수

 

- 어느 페이지에 있던 '>>' 를 클릭시 가장 마지막 페이지로 이동

- 가장 마지막 페이지인 13 페이지로 이동했다

+ 잘못된 표 수정했다

 

+ 문제점

- 13 페이지가 마지막 페이지이다

- 실제 존재하지 않는 페이지 14,15.. 가 나타난다

- for문으로 endPage 정의 부분에서 문제가 생김

- 1블럭이면 무조건 1 ~ 10페이지, 2블럭이면 무조건 11 ~ 20 페이지를 출력하므로 마지막 페이지에 문제가 생김

 

문제 해결

- 가장 마지막 페이지의 endPage 값을 pageCount 값으로 설정해야함

	// 가장 마지막 블럭에 endPage값을 pageCount로 수정
	if(endPage > pageCount) {	//
		endPage = pageCount;
	}

- pageCount는 13 이고, 1블럭일때, endPage 는 10 이다.

- pageCount는 13 이고, 2블럭일때, endPage 는 20 이다.

- 즉, 2블럭에서 endPage > pageCount 를 만족하므로, 마지막 블럭인 2블럭에서 endPage 값은 pageCount값이 됨

-' >>' 를 눌렀을때 캡처

 

이전 블럭과 다음 블럭으로 이동

- '[이전]' 을 눌렀을때 이전 블럭으로, '[다음]' 를 눌렀을때 다음 블럭으로 넘어가자

- 조건문을 써서 이전 블럭과 다음 블럭이 존재하는 경우에만 '이전' 과 '다음' 이 보이도록 하자

 

이전 블럭으로 이동 (list.jsp 부분)

	// 이전 블럭으로 이동
	if(startPage > 10){
		<a href="list.jsp?page=<%=startPage-10 %>">[이전]</a>
	}

- startPage 가 10 보다 큰 경우만, 즉 2블럭 이상만 이전 블럭 메뉴가 나타나도록 함

- 첫번째 블럭에서는 startPage 가 1 이므로 [이전] 메뉴가 나타나지 않음

- '[이전]' 블럭의 가장 작은 페이지로 이동하자

- 두번째 블럭에 있을때, startPage 는 11이다, 그 값에서 10 을 빼면 이전 블럭의 가장 작은 페이지인 1 로 이동

- 세번째 블럭에 있을때, startPage 는 21이다, 그 값에서 10 을 빼면 이전 블럭의 가장 작은 페이지인 11 로 이동

 

 

다음 블럭으로 이동 (list.jsp 부분)

	// 다음 블럭으로 이동
	if(endPage < pageCount) {	
		<a href="list.jsp?page=<%=startPage+10 %>">[다음]</a>	
	}

- endPage 가 pageCount 보다 작은 경우 = 다음 블럭이 존재하는 경우

- 첫번째 블럭은 endPage 가 10, pageCount 는 13 이므로 조건식 만족, [다음] 나타남

- 마지막 블럭인 두번째 블럭은 endPage 가 20. pageCount 는 13 이므로 조건식 불만족, 즉 마지막 페이지에서는 [다음] 나타나지 않음

- '[다음]' 블럭의 가장 작은 페이지로 이동하자

- 첫번째 블럭에 있을때, startPage는 1이다, 10을 더하면 다음 블럭의 가장 작은 페이지인 11 로 이동

- 두번째 블럭에 있을때, startPage는 11이다, 10을 더하면 다음 블럭의 가장 작은 페이지인 21 로 이동

 

 


list.jsp 코드 (최종)

- 목록을 나타냄

- 페이징 처리 적용

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="board.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>

<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호가 저장되는 변수
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 출력할 데이터의 시작번호
	// endRow : 각 page에 출력할 데이터의 끝번호
	// page = 1 : startRow=1, endRow=10
	// page = 2 : startRow=11, endRow=20
	// page = 3 : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		// 게시판 목록
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list : " + list);
/* 	System.out.println(list.get(1).getNum()); */

	if(count == 0) {
%>
		작성된 글이 없습니다.
<% 	} else {	%>
		<a href="writeForm.jsp">글작성</a>  글갯수 : <%=count %>
		
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<td>번호</td>
				<td>제목</td>
				<td>작성자</td>
				<td>작성일</td>
				<td>조회수</td>
				<td>IP주소</td>
			</tr>
<%
			SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage - 1) * page_size;
// page=1 :     number =  130  - (    1       - 1) *     10   ; -> 130
// page=2 :     number =  130  - (    2       - 1) *     10   ; -> 120

			for(int i = 0 ; i < list.size() ; i++) {
				BoardDataBean board = list.get(i);
%>				
				<tr>
					<td><%=number-- %></td>
					<td><%=board.getSubject() %></td>
					<td><%=board.getWriter() %></td>
					<td><%=sd.format(board.getReg_date()) %></td>
					<td><%=board.getReadcount() %></td>
					<td><%=board.getIp() %></td>
				</tr>
<%			

			
			}	// for end
%>
		</table>
<%	}%>

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	// pageCount : 총 페이지 수
	int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
	// endPage : 각 블럭의 끝 페이지 번호 :    10, 20, 30 ...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에 endPage값을 pageCount로 수정
	if(endPage > pageCount) {	//
		endPage = pageCount;
	}
%>	
	<!-- 1page로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
	// 이전 블럭으로 이동
	if(startPage > 10){	%>
		<a href="list.jsp?page=<%=startPage-10 %>">[이전]</a>
<%	}

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) { %>
			[<%=i %>]
<%		} else {%>
			<a href="list.jsp?page=<%=i %>">[<%=i %>]</a>
<%		}
	}// for end
	
	// 다음 블럭으로 이동
	if(endPage < pageCount) {
%>	
		<a href="list.jsp?page=<%=startPage+10 %>">[다음]</a>	
<%	} %>
	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" style="text-decoration:none"> >> </a>	
<%}%>
</center>

</body>
</html>

페이징 처리 변수 정리

기본변수

1. page_size : 한 화면에 출력할 데이터 갯수

2. currentPage : 현재 페이지 번호가 저장되는 변수

3. count : 총 데이터 갯수

 

파생변수

1. startRow : 각 page에 출력할 데이터의 시작번호

2. endRow : 각 page에 출력할 데이터의 끝번호

ex) page = 1 : startRow=1, endRow=10
ex) page = 2 : startRow=11, endRow=20
ex) page = 3 : startRow=21, endRow=30

3. number : 각 페이지에 출력될 시작 번호

4. pageCount : 총 페이지 수

5. startPage : 각 블럭의 시작 페이지 번호 ex) 1, 11, 21 ...
6. endPage : 각 블럭의 끝 페이지 번호 ex) 10, 20, 30 ...



- 제목에 링크를 걸어서 그 글에 대한 상세 페이지 content.jsp 로 넘기자

 

목록에서 특정 글 제목 클릭시 상세 페이지로 넘어가기 (list.jsp 부분)

<td>
	<a href="content.jsp?num=<%=board.getNum()%>&page=<%=currentPage%>">
			<%=board.getSubject() %>
	</a>		
</td>

- get 방식으로 글 번호 와 현재 page 번호를 가져가야한다

- 글 번호는 어떤 글의 상세정보를 보여주기 위해 , DB 검색위해서 필요

- page 번호는 글을 본 후 다시 게시판 목록으로 돌아올때 어떤 페이지로 돌아올지 알수있다

ex) 5 페이지에서 어떤 글을 눌러서 글의 상세페이지로 간 다음 돌아올 때 5 페이지로 돌아오기

 

- list.jsp 에서 제목을 눌러서 상세 페이지 content.jsp 로 이동

- get 방식으로 내부 글번호와 페이지 번호를 content.jsp 로 전달한다


일반게시판 : 글 상세페이지

상세페이지에서 할 일

1. 조회수 증가

2. 전달받은 num 값으로 DB 연동해서 글의 상세정보 출력

 

- content.jsp

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 조회수 1 증가 + 상세정보 구하기
	BoardDataBean board = dao.updateContent(num);
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=500 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>번호</td>
		<td><%=board.getNum() %></td>
		<td>조회수</td>
		<td><%=board.getReadcount() %></td>
	</tr>
	<tr>
		<td>작성자</td>
		<td><%=board.getWriter() %></td>
		<td>작성일</td>
		<td><%=board.getReg_date() %></td>
	</tr>
	<tr>
		<td>제목</td>
		<td colspan=3><%=board.getSubject() %></td>
	</tr>
	<tr>
		<td>내용</td>
		<td colspan=3><%=board.getContent() %></td>
	</tr>
	<tr>
		<td colspan=4 align=center>
			<input type="button" value="글수정">
			<input type="button" value="글삭제">
			<input type="button" value="글목록">
		</td>
	</tr>
</table>
</body>
</html>

1. 전달된 num값(글번호)과 페이지번호를 가져온다

- DAO 메소드의 SQL문에서 setInt()를 할때 int형 값이 들어가야하므로 전달된 num값을 int형으로 형변환 해야한다.

- 페이지 지시어 태그와 키워드가 일치하므로 변수명으로 page 를 쓰지 못함, nowpage 로 페이지번호를 받자

2. 조회수 1 증가 + 상세정보 구하기를 역할을 하는 DAO의 메소드 updateContent() 를 호출하여 DB와 연동하자

- 글 번호인 num 을 매개변수로 넘겨줘서 그 글에 대한 정보를 구할 수 있도록 하자

- 리턴자료형을 DTO 클래스로 해서 상세정보를 받자

+ 돌려받을 상세정보는 한개의 글에 대한 (한개의 데이터) 상세정보이므로 DTO 가 리턴자료형

- 이후 돌려받은 상세정보를 테이블에 뿌려주고 있다

 

- 이제 DAO 클래스에서 DB 와 연동하여 조회수 1 증가 + 상세정보 구하기를 해줄 updateContent() 메소드를 성하자

 

DAO 클래스 조회수 1 증가 & 상세정보 구하기 메소드 작성

- 메소드 updateContent() 에서 2개의 sql문을 실행해야한다

1. 조회수 1 증가 = update문

2. 상세정보 구하기 = select문

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
	
	// 상세 페이지 : 조회수 1 증가 + 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// 첫번째 SQL문 실행
			
			sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}
}

- 검색결과가 1개의 데이터이므로 DTO 가 리턴자료형, DTO 객체를 돌려줘야한다

- DTO 객체를 가장 위에서 만들고 거기에 검색결과 상세정보 저장할 것

<조회수 1 증가>

- 첫번째 SQL문으로 조회수 값을 1 증가시키기 위한 update 문을 작성한다

<상세정보 구하기>

- 두번째 SQL문으로 상세정보를 구하기 위한 select 문을 작성한다

- 1개의 데이터이므로 if문과 rs.next 로 데이터를 가져온다

- rs 에서 가져온 값을 DTO 객체에 저장한다

+ 컬럼 자료형에 따라 getInt(), getString(), getTimestamp() 등 다른 메소드 사용

 

- 추가한 부분 코드만

 

- 완성

- list.jsp 를 먼저 실행해서 특정 글의 제목을 클릭해보자

- 글 수정 / 삭제시 글번호값과 페이지번호값을 3번을 가져갈 것인데, 출발점이 list.jsp 이다

- URL 에서 글번호 num 과 페이지번호 page 가 넘어왔음을 확인 가능

- '글목록' 을 눌렀을때 원래 페이지로 돌아가기 위해 페이지번호 page 가 필요

 

줄바꿈 문제점

- '내용' 인 textarea 에 여러줄을 입력해도 출력시엔 <td> 태그 안에 출력되니 한줄로 나타난다

 

 

줄바꿈 문제 해결 두가지 방법

1. <pre> 태그로 감싸주기

- <pre> 태그로 감싸주면 줄이 바뀐다

 

2. replace() 로 치환하기

- "\n" 을 "<br>" 로 치환하기

- content 를 출력시 제대로 줄바꿈이 된다

 

날짜 포맷 문제점 해결

- 원하느 날짜 시간 포맷으로 설정하기

- SimpleDateFormat 객체 생성

 

- 이제 세가지 버튼 클릭 이벤트를 처리하자

 

버튼 이벤트 처리

 

1. 글목록 버튼 이벤트 처리

- 페이지번호를 가져왔기 때문에 원래 페이지로 돌아갈 수 있다

- 페이지번호를 가져오지 않았더라면 1 페이지로만 돌아갈 수 있다

- Lay's diary 글에서 '글목록' 클릭시 13 페이지로 돌아오는 것을 볼 수 있다

+ 조회수가 1 증가했음도 확인 가능

+ 한번 더 Lay's diary 클릭시 조회수가 다시 1 증가

 

+ 전체 흐름

 

2. 글수정 버튼 이벤트 처리

- 글수정 을 눌러서 수정 폼으로 넘어갈때, 글삭제 를 눌러서 삭제 폼으로 넘어갈때도 계속 글번호와 페이지번호를 전달

- 먼저 content.jsp 에서 이벤트 처리 해주기


일반게시판 : 글 수정

 

- 수정폼 updateForm.jsp 생성 후 작성

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>

<html>
<head>
	<title>글수정</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
	<script src="check.js"></script>
</head>   
ㄴ
<body bgcolor="<%=bodyback_c%>">  
<center><b>글수정</b>
<br>
<form method="post" name="writeform" action="writePro.jsp">

<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" name="writer"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">    
       <input type="text" size="40" maxlength="50" id="subject" name="subject"></td>	
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" name="email" ></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"></textarea> </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글쓰기" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp'">
</td></tr></table>    
   
</form>      
</body>
</html>

- wirteForm.jsp 파일 내용을 전부 복붙후 수정

1. 가장 먼저 윗부분에서 content.jsp 에서 넘어온 글번호값과 페이지번호값을 받음

- 나중에 setInt() 메소드를 사용해야하므로 글번호값을 미리 int 형으로 형변환함

2. 상세정보를 구해오기 위한 DAO의 getContent() 메호드 호출

- 상세정보를 구할때 글번호가 필요하므로 num 을 매개변수로 넘긴다

- 상세정보를 담아올 DTO 객체가 필요하므로 리턴자료형을 DTO 로 한다

- 앞의 updateContent() 는 조회수 증가 + 상세정보 구하기 둘다 하는 메소드였다

- 이번에는 getContent() 메소드로 상세정보만 구해오는 메소드를 호출

3. 이후 받아온 값을 가공하는 부분은 DAO 클래스에 메소드를 작성한 후 다시 돌아와서 작성

- 아래에 이어서 작성

 

- DAO 클래스에 getContent() 메소드를 작성하자

 

DAO 클래스 상세정보 구하기 메소드 작성

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
	
	// 상세 페이지 : 조회수 1 증가 + 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// 첫번째 SQL문 실행
			
			sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}
	
	// 수정 폼 : 상세 정보 구하기
	public BoardDataBean getContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;		
		
		try {
			con = getConnection();
			
			String sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} 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 board;		
	}
}

- updateContent() 메소드에서 조회수 증가 부분만 제외하면 똑같다

- 추가한 부분 코드만

 

- 다시 updateForm.jsp 로 돌아와서 돌려받은 값을 화면에 뿌려주자

- updateForm.jsp

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	// 상세 정보 구하기
	BoardDataBean board = dao.getContent(num);
%>

<html>
<head>
	<title>글수정</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
	<script src="check.js"></script>
</head>   
ㄴ
<body bgcolor="<%=bodyback_c%>">  
<center><b>글수정</b>
<br>
<form method="post" name="writeform" action="updatePro.jsp">
<input type="hidden" name="num" value="<%=num %>">
<input type="hidden" name="page" value="<%=nowpage %>">

<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" 
       name="writer" value="<%=board.getWriter() %>"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">    
       <input type="text" size="40" maxlength="50" id="subject" 
       name="subject" value="<%=board.getSubject() %>"></td>	
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" 
       name="email" value="<%=board.getEmail() %>"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"><%=board.getContent() %></textarea> </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글수정" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" 
  OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
   
</form>      
</body>
</html>

- writeForm.jsp에서 어느 부분을 수정했는지 아래에 설명

 

- action 값을 updatePro.jsp 로 수정, 타이틀 수정 해주기

- 글목록 클릭시 원래의 페이지로 돌아가도록 하기

- 이름, 제목, 이메일에 value 속성을 추가해서 수정폼에서 그 부분이 나타나도록하기

+ 비밀번호는 사용자가 수정폼에 직접 입력해야하므로 value 속성 추가하지 않기

- 목록보기에서 원래 페이지로 돌아가게끔 get 방식으로 페이지번호 넘기기

 

글번호와 페이지번호 넘기기 (updateForm.jsp 부분)

- 글수정을 하기위해 updatePro.jsp 로 넘어갈때, 글번호 값인 num 을 hidden 으로 updatePro.jsp 로 넘겨줘야함

- 수정 완료 후 돌아올때 원래 페이지로 돌아가기 위해 nowpage 를 hidden 으로 updatePro.jsp 로 넘겨줘야함

- 글번호, 페이지번호는 화면에 보이지 않지만 넘어가야하므로 hidden 으로 post 방식으로 넘겨주기

- 글번호, 페이지번호가 3번째 전달되고 있다, 시작은 list.jsp 에서 시작

- DB 에서 비번 끄집어낼때, DB 에서 update 실행할때 글번호인 num 이 필요

- 수정 완료 후 다시 목록페이지로 돌아가야하는데, 이때 원래 페이지로 돌아가기 위해 페이지번호인 page 가 필요

 

 

- 이제 수정폼 기능이 끝났고 진짜 수정(update) 기능을 구현하자

- 수정폼에서 입력한 비밀번호가 일치해야 수정이 되도록 할것인데, 비밀번호가 일치하는지 확인하는 곳

- updatePro.jsp 파일을 생성 후 작성

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="board.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시

		int result = dao.update(board);	// update SQL문 실행
		if(result == 1) {
%>			
			<script>
				alert("글 수정 성공");
				location.href="list.jsp?page=<%=nowpage%>";
			</script>
<%		} 
	} else { // 비번 불일치시 %>
		<script>
			alert("비번이 일치하지 않습니다.");
			history.go(-1);
		</script>
<%	}	%>

1. 한글값이 post 값으로 전달되므로 한글 인코딩

2. useBean action tag, setProperty action tag 로 수정할 내용, 즉 수정폼에서 사용자가 입력한 값을 DTO 객체 board 를 생성해서 저장

3. 페이지번호인 page 는 DTO 클래스의 프로퍼티가 아니기때문에 setProperty 로 DTO에 저장 불가능하므로 따로 다른 변수에 받아야함

4. DB 에서 비밀번호를 가져와서 사용자가 수정폼에 입력한 비밀번호와 맞는지 확인해야하므로 DAO 객체 먼저 생성

5. DTO 객체 board 에서 getNum() 으로 글번호값을 구해와서, DAO 클래스의 getContent() 의 매개변수를 넣어서 구해진 그 글의 상세정보를 새로운 DTO 객체 old 에 저장

- 수정폼에서 사용했던 이미 만들어진 메소드인 getContent()

6. DB 에서 구해진 상세정보를 저장하는 old 객체의 비밀번호 old.getPasswd() 와, 사용자가 수정폼에 입력한 정보를 저장하는 board 객체의 비밀번호 board.getPasswd() 가 같은지 비교

7. 비번이 일치하는 경우 DAO 클래스의 update() 메소드를 호출해서 수정

- 매개변수는 사용자가 수정폼에 입력한 값들을 저장하는 DTO 객체 board 를 넘겨주고 있다

- 업데이트에 성공한 데이터의 개수를 리턴받고 있다

- 비번 일치 후 수정까지 성공시 목록페이지의 원래 페이지로 돌아간다

8. 비번이 일치하지 않는 경우는 이전 페이지인 수정폼 페이지로 돌아간다

 

- DAO 클래스에서 update() 메소드 작성하자

 

DAO 클래스 수정 메소드 작성

- update() 메소드를 작성하자

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
	
	// 상세 페이지 : 조회수 1 증가 + 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// 첫번째 SQL문 실행
			
			sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}
	
	// 수정 폼 : 상세 정보 구하기
	public BoardDataBean getContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;		
		
		try {
			con = getConnection();
			
			String sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} 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 board;		
	}
	// 글수정
	public int update(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			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 객체 중 hidden 으로 넘어왔던 글번호 board.getNum() 을 where 조건절에 넣기

- 특정 데이터를 수정

 

- 추가한 부분 코드만

 

- list.jsp 를 먼저 실행하고 수정하고자 하는 글의 제목을 클릭했을때

- 수정폼에서 맞는 비번을 입력할 때

- 목록페이지의 원래 페이지로 돌아왔다

- 수정폼에서 틀린 비번을 입력할 때

 

+ 수정 성공시 목록페이지가 아닌 상세페이지로 가게끔 처리도 가능

- 상세페이지 content.jsp 로 이동하기 위해 updatePro.jsp 부분을 수정

- content.jsp 에서는 2개의 값을 받고 있기때문에 상세페이지로 가려면 글번호와 페이지번호 get 방식 전달


일반게시판 : 글 삭제

 

3. 글삭제 버튼 이벤트 처리

- 글수정 을 눌러서 수정 폼으로 넘어갈때, 글삭제 를 눌러서 삭제 폼으로 넘어갈때도 계속 글번호와 페이지번호를 전달

- 먼저 content.jsp 에서 이벤트 처리 해주기

 

- 삭제 폼인 deleteForm.jsp 파일 생성 후 작성

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<%
	int num = Integer.parseInt(request.getParameter("num"));
	String nowpage = request.getParameter("page");
%>

<html>
<head>
	<title>글삭제</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
	<script src="check.js"></script>
</head>   
<body bgcolor="<%=bodyback_c%>">  
<center><b>글삭제</b>
<br>
<form method="post" name="writeform" action="deletePro.jsp">
<input type="hidden" name="num" value="<%=num %>">
<input type="hidden" name="page" value="<%=nowpage %>">

<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp?page<%=nowpage%>"> 글목록</a> 
   </td>
   </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글삭제" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" 
  OnClick="window.location='list.jsp?page=<%=nowpage%>'">
</td></tr></table>    
   
</form>      
</body>
</html>

- updateForm.jsp 의 내용을 복사 후 많은 부분 삭제, 수정

- hidden 으로 글번호, 페이지번호 넘기는 부분은 그대로 남기기

- 삭제폼에서는 비밀번호 입력란만 있으면 되므로 다른 입력양식은 지우기

- '글수정' -> '글삭제' 로 변경, action 을 deletePro.jsp 로 수정

- 여기서는 수정폼처럼 상세정보를 구해서 뿌려줄 필요가 없으므로 DB 연결 부분 삭제

+ 비밀번호 비교는 deletePro.jsp

 

- deletePro.jsp 에서 DB 와 연동해서 비밀번호 비교를 하기 위해 글번호인 num 을 전달해야함

- deletePro.jsp 에서 삭제 후 원래 페이지로 돌아가기 위해 페이지번호 page 를 전달해야함

 

삭제 폼에서 넘어가는 3가지 값

1. 글번호 num

2. 페이지번호 page

3. 사용자가 삭제폼에서 입력한 비밀번호 passwd

 

- 삭제폼에서 넘어온 비밀번호가 맞는지 DB 비밀번호와 비교후 진짜 삭제(delete) 를 해주는 deletePro.jsp 생성 후 작성

- deletePro.jsp

<%@page import="board.BoardDataBean"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="board.BoardDataBean"/>
<jsp:setProperty property="*" name="board"/>

<%
	String nowpage = request.getParameter("page");
	
	BoardDBBean dao = BoardDBBean.getInstance();
	
	BoardDataBean old = dao.getContent(board.getNum());
	
	// 비번 비교
	if(old.getPasswd().equals(board.getPasswd())) {	// 비번 일치시

		int result = dao.delete(board.getNum());	// update SQL문 실행
		if(result == 1) {
%>			
			<script>
				alert("글 삭제 성공");
				location.href="list.jsp?page=<%=nowpage%>";
			</script>
<%		} 
	} else { // 비번 불일치시 %>
		<script>
			alert("비번이 일치하지 않습니다.");
			history.go(-1);
		</script>
<%	}	%>

- updatePro.jsp 를 복붙 후 수정

- update() 를 delete() 로 수정 후 매개변수로는 글번호만 있으면 되므로 board 에서 board.getNum() 으로 변경

- 비번 일치 후 글 삭제 성공시 목록페이지인 list.jsp 의 원래 페이지로 돌아간다

 

- 이제 DAO 클래스에서 delete() 메소드를 작성하자

 

DAO 클래스 삭제 메소드 작성

- delete() 메소드 작성

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
	
	// 상세 페이지 : 조회수 1 증가 + 상세정보 구하기
	public BoardDataBean updateContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set readcount=readcount+1 ";
			sql += "where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			pstmt.executeUpdate();	// 첫번째 SQL문 실행
			
			sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
			
		} 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 board;
	}
	
	// 수정 폼 : 상세 정보 구하기
	public BoardDataBean getContent(int num) {
		BoardDataBean board = new BoardDataBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;		
		
		try {
			con = getConnection();
			
			String sql = "select * from board0 where num=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			rs = pstmt.executeQuery();	// 두번째 SQL문 실행
			
			if(rs.next()) {		// 조건식을 만족하는 데이터 1개를 가져온다.
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
			}
		} 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 board;		
	}
	
	// 글수정
	public int update(BoardDataBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update board0 set writer=?,email=?,subject=?,";
			sql += "content=? where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getContent());
			pstmt.setInt(5, board.getNum());
			
			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;
	}
	
	// 글 삭제
	public int delete(int num) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "delete from board0 where num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, num);
			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;
	}
}

- 글번호만 매개변수로 넘어오므로 매개변수의 자료형은 int

- 삭제에 성공한 데이터 개수를 돌려줄 것이므로 리턴 자료형은 int

- update() 메소드의 내용을 복붙 후 수정

- SQL문 부분을 delete문으로 수정하면 된다

 

- 추가된 부분 코드만

 

- list.jsp 로 먼저 가서 126 번 글 클릭

- 126번 글을 삭제해보자


index.jsp

- index.jsp 는 가장 먼저 자동으로 실행되는 파일이므로 여기에서 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>
	<script>
		location.href="board/list.jsp";
	</script>
</body>
</html>

- index.jsp 파일을 기준으로 list.jsp 파일은 board 폴더 안에 있으므로 board/list.jsp 를경로로 적음

- index.jsp 실행시 list.jsp 로 바로 이동함

System.out.print() vs out.print()

- System.out.print() 는 JAVA 코드이고 콘솔창에 출력됨

- out.println() 은 JSP 코드이고 브라우저에 출력됨

 

id값 넘기기

- 수정폼에서 id 는 input=text 가 아니기때문에 넘어가지 않으므로 hidden 객체로 넘겨야한다

- hidden 으로 값을 넘기므로, update.jsp 에서 세션으로부터 id 를 구해오지 않아도 됨


 

회원관리 프로그램 : 정보수정 기능 (이어서)

- 이전에는 수정폼을 했다

- 이젠 수정폼에서 '회원수정' 버튼을 눌렀을때의 진짜 수정(update) 기능 을 구현할 것

- 넘어온 id 값으로 비밀번호를 끄집어내서 일치할때 수정을 해야한다

- update.jsp

<%@page import="member.MemberDAO"%>
<%@page import="member.MemberDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="member" class="member.MemberDTO"/>
<jsp:setProperty property="*" name="member"/>

<%
	String[] hobby = request.getParameterValues("hobby");

	// 취미를 하이픈(-)을 붙여서 저장 : ex) 공부-게임-등산-
	String h = "";		// h = "공부-게임-등산-"
	for(String h1 : hobby) {
		h += h1 + "-";
	}
	
	member.setHobby(h);
	
	MemberDAO dao = MemberDAO.getInstance();
	
	// 한 사람의 상세 정보 구하기
	MemberDTO old = dao.getMember(member.getId());
	
	// 비번 비교
	if(old.getPasswd().equals(member.getPasswd())) {	// 비번 일치시
		int result = dao.update(member);	// update SQL문 실행
		
		if(result == 1) {
%>
			<script>
				alert("회원정보 수정 성공");
				location.href = "main.jsp";
			</script>

<%		}
	} else { // 비번 불일치시 %>
		<script>
			alert("비밀번호가 일치하지 않습니다.");
			history.go(-1);
		</script>
<% 	} %>

<수정폼에서 넘어온 값 받아서 DTO 객체에 저장>

- 한글값이 넘어올 수 있으므로 인코딩

- 수정폼에서 넘어온 값을 저장하기 위한 DTO 객체 member 를 생성 = 힙메모리에 값들이 저장됨

- setProperty 를 사용해서 DTO 객체 member 에 값 저장

- 수정폼에서 넘어어온 '취미' 는 체크박스이므로 따로 getParamterValues() 메소드로 따로 받아야한다

- member.jsp 에서 했던 것 처럼 취미는 따로 받은 후에 "-"(하이픈) 을 붙여서 setHobby() 로 객체에 저장해야함

<수정전 비밀번호 일치 확인>

- 가장 처음으로, 넘어온 id 값으로 DB 의 비밀번호를 끄집어내서 수정폼의 비밀번호와 일치한지 확인해야한다

- DB 와 연동할 DAO 의 메소드를 사용해야하므로 DAO 객체를 만든다

- 한명의 상세정보를 구하는 메소드가 필요하므로, 수정폼에서 사용했던 메소드인 getMember() 를 사용한다

- getMember() 의 리턴자료형은 DTO, 즉 한명에 대한 상세정보를 DTO 객체 old 로 반환

- old.getPasswd() 는 DB 의 비밀번호이고, member.getPasswd() 는 수정폼에서 사용자가 입력한 비밀번호이다, 두개가 일치하는지 확인한다

- 비번 일치시 update 를 하는 DAO 의 메소드인 update() 를 호출하고 결과를 int result 로 돌려받는다

- 비번 불일치시 수정폼으로 돌아간다

 

- 이제 DAO 클래스에서 update() 메소드를 작성하자

 

DAO 클래스 회원 수정하기

- update() 메소드 작성

- MemberDAO.java (추가)

// DAO (Data Access Object)

package member;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO 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();		
	}
	
	// ID 중복 검사
	public int memberAuth(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션 풀에서 커넥션을 구해옴
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {	// 조건식을 만족하는 데이터를 구해옴
				result = 1;
			}
			
		} catch(Exception e) {
			
		} 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;
	}
	
	
	// 회원 가입
	public int insert(MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "insert into member ";
			sql += " values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());
			
			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;
	}
	
	// 로그인
	public int memberCheck (MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {			// ID 일치
				if(rs.getString("passwd").equals(member.getPasswd())) {
					result = 1;		// ID, 비번 일치 (회원인증 성공)
				} else {
					result = -1;	// 비번 불일치
				}
			} else {				// ID 불일치
					result = -2;
			}			
		} 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;
	}
	// 회원 수정폼 : 한 사람의 상세정보 구하기
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
//				member.id = "test";	// 접근 안됨(private 접근제어자)
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} 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 member;
	}
	
	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "update member set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql +="domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql +="post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());
			
			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;
	}
}

<update() 메소드 형식 작성>
- 수정폼에서 사용자가 입력한 값을 저장하는 DTO 객체를 매개변수로 받을 것이므로 자료형은 DTO

- 업데이트 된 개수를 돌려줄 것이므로 int 형으로 리턴

<update() 메소드 내용 작성>

1. Connection, PreparedStatement 객체 생성

2. try-catch-finally 문을 만들고 try 안에서 getConnection() 으로 커넥션 풀에서 커넥션을 구해옴

3. 아이디와 비밀번호를 제외한 정보를 변경하는 것이므로 update sql 문에 id, passwd 는 쓰지 않음

- where 조건절에 id 를 써서, 원하는 데이터 1개(1명) 만 수정할 수 있도록 한다

4. Connection 객체 con 으로 작성한 SQL 문을 읽어서 PreparedStatement 객체 생성

5. update 하고자하는 값을 매개변수로 받은 객체 member 에서 값을 가져와서 setString() 으로 ? 에 설정

6. sql 문을 executeUpdate() 메소드로 실행

- update sql 문이 잘 실행되면 1을 돌려준다

- update 를 하면 끝

7. PreparedStatement 객체, Connection 객체 닫기

 

+ 다른 메소드 더 추가할 것, 아직 DAO 미완성

 

- 추가한 update() 부분 코드만

 

- 로그인 폼부터 로그인 하고 메인페이지에서 '정보수정' 을 누르고 수정폼에서 정보를 입력후 '회원수정' 을 클릭시 실행됨

- 맞는 비밀번호 입력시

- 다시 수정폼에 들어가면 회원정보가 수정되어있음을 확인 가능

 

- 틀린 비밀번호 입력시

- 그 후 다시 수정폼으로 돌아감

 

- 이제 회원탈퇴 관련 기능을 구현하자

- 우선 메인페이지에서 삭제폼인 deleteform.jsp 로 링크 걸기

 

회원탈퇴 기능 링크 연결

- main.jsp

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

<%
	String id = (String) session.getAttribute("id");
	if(id != null) {	// 세션이 있는 경우 (정상적인 로그인)
%>
		<%=id %>님 환영합니다. <br><br><br>
		
		<a href="updateform.jsp">정보수정</a> <br>
		<a href="logout.jsp">로그아웃 </a> <br>
		<a href="deleteform.jsp">회원탈퇴</a> <br>
<%	} else { 			// 세션이 없는 경우 %>			
		<a href="memberform.html">회원가입</a> <br>
		<a href="loginform.html">로그인</a> <br>
<%	}	%>

 

- 이제 deleteform.jsp 파일을 생성하자

 

회원관리 프로그램 : 회원탈퇴 기능

- 먼저 deleteform.jsp 라는 삭제폼을 만들것

- deleteform.jsp

<%@page import="member.MemberDAO"%>
<%@page import="member.MemberDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%
	String id = (String) session.getAttribute("id");
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 삭제 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="member.js"></script>

</head>
<body>

<form method="post" action="delete.jsp"> 
<input type="hidden" name="id" value="<%=id %>">
<table border=1 width=450 align="center">
	<caption>회원 삭제 폼</caption>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd">
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원삭제">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>

</body>
</html>

- deleteform.jsp는 updateform.jsp 파일의 내용을 복붙한 다음 필요없는 대부분 지우기

- 삭제폼에서 한명의 상세정보를 가져올때 했었던 DB 연동도 필요하지 않다

- 비밀번호 일치 확인은 deleteform.jsp 가 아닌 delete.jsp 에서 할 것

- 세션에서 받아온 id 를 받아서 form 을 통해 hidden 으로 delete.jsp 로 전송한다

- 이렇게 delete.jsp 로 넘어간 id 값을 delete.jsp 에서 사용자가 삭제폼에 입력한 비밀번호와 DB 에서 비밀번호가 일치하는지 확인하는데 사용할 것

- 비밀번호 입력양식만 남긴다

 

- 로그인 후 main.jsp 에서 '회원탈퇴' 누르며 삭제 폼이 나타난다

 

- 다음으로 DB 와 연동하여 비밀번호를 확인한 후 삭제를 수행하는 delete.jsp 를 생성 후 작성하자

- delete.jsp

<%@page import="member.MemberDAO"%>
<%@page import="member.MemberDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="member" class="member.MemberDTO"/>
<jsp:setProperty property="*" name="member"/>

<%
	MemberDAO dao = MemberDAO.getInstance();
	
	// 한 사람의 상세 정보 구하기
	MemberDTO old = dao.getMember(member.getId());
	
	// 비번 비교
	if(old.getPasswd().equals(member.getPasswd())) {	// 비번 일치시
		int result = dao.delete(member.getId());	// update SQL문 실행
		
		if(result == 1) {
			session.invalidate();	// 세션 삭제
%>
			<script>
				alert("회원 탈퇴 성공");
				location.href = "main.jsp";
			</script>

<%		}
	} else { // 비번 불일치시 %>
		<script>
			alert("비밀번호가 일치하지 않습니다.");
			history.go(-1);
		</script>
<% 	} %>

- update.jsp 의 내용을 복붙해서 수정, 취미 부분 지우기

- 삭제폼에서 넘어온 id 와 비밀번호를 DTO 객체인 member 를 생성하여 저장

- hidden 으로 넘어온 id 값으로 DB 의 비밀번호를 가져와야하므로, DAO의 getMember() 를 그대로 사용

- 비밀번호 비교하는 부분은 그대로 두기, DAO 클래스의 delete() 메소드로 삭제

- delete() 메소드를 수행할때 필요한 id 값을 매개변수로 전달

- delete() 메소드로 회원정보 삭제가 성공시 세션을 삭제시켜야함!

- 삭제 성공시 세션 삭제 후 main.jsp 로 가는데, 세션정보가 없으므로 '회원가입' '로그인' 등의 메뉴가 뜨는 화면으로 간다

- 삭제된 데이터 개수를 int 형 변수 result 로 받아서 저장

 

- 실제로 delete 를 수행해주는 DAO 클래스의 delete() 를 작성하자

 

DAO 클래스 회원 탈퇴 메소드

- delete() 메소드 작성

- MemberDAO.java (추가)

// DAO (Data Access Object)

package member;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO 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();		
	}
	
	// ID 중복 검사
	public int memberAuth(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션 풀에서 커넥션을 구해옴
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {	// 조건식을 만족하는 데이터를 구해옴
				result = 1;
			}
			
		} catch(Exception e) {
			
		} 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;
	}
	
	
	// 회원 가입
	public int insert(MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "insert into member ";
			sql += " values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());
			
			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;
	}
	
	// 로그인
	public int memberCheck (MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {			// ID 일치
				if(rs.getString("passwd").equals(member.getPasswd())) {
					result = 1;		// ID, 비번 일치 (회원인증 성공)
				} else {
					result = -1;	// 비번 불일치
				}
			} else {				// ID 불일치
					result = -2;
			}			
		} 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;
	}
	// 회원 수정폼 : 한 사람의 상세정보 구하기
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "select * from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
//				member.id = "test";	// 접근 안됨(private 접근제어자)
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} 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 member;
	}
	
	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "update member set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql +="domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql +="post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());
			
			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;
	}
	
	// 회원정보 삭제
	public int delete(String id) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();	// 커넥션풀에서 커넥션을 구해온다
			
			String sql = "delete from member where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			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;
	}
}

<delete() 메소드 형식 작성>

- 매개변수는 패스워드가 일치한 경우 그 사람의 아이디가 넘어가야하므로 String 형

- 리턴자료형은 삭제된 데이터의 개수를 돌려받을 수 있는 int 형

<delete() 메소드 내용 작성>

1. Connection, PreparedStatement 객체 생성

2. try-catch-finally 문을 만들고 try 안에서 getConnection() 으로 커넥션 풀에서 커넥션을 구해옴

3. 삭제하기 위해 데이터를 식별하는 id 만 있으면 되므로 매개변수로 넘겨받은 id 값을 sql 문에 적기

4. Connection 객체 con 으로 작성한 SQL 문을 읽어서 PreparedStatement 객체 생성

5. delete 하고자하는 데이터를 매개변수로 받은 id 로 구분하므로 ? 에 id 를 세팅

6. sql 문을 executeUpdate() 메소드로 실행

- delete sql 문이 잘 실행되면 1 을 리턴

7. PreparedStatement 객체, Connection 객체 닫기

 

- 추가한 delete() 부분 코드만

- 잘 구현되었는지 확인하기 위해 삭제해보자 

 

- 틀린 비밀번호 입력시

- 맞는 비밀번호 입력시

- DB 에서 회원 삭제(탈퇴) 성공시 세션이 끊겨서 세션이 없으므로 main.jsp 에서 이 화면을 보여준다

 

+ 주의해야하는 부분

- 아이디, 비밀번호를 넘길때 공백이 들어가지 않도록 주의한다

ex) 비번으로 "1999 " 을 입력시 DB 의 "1999" 와 다른 비밀번호로 인식

 

- JSP Model 1 회원관리 프로그램 완성되었다

- 다음은 JSP로 일반게시판을 만들 것


Model 1 DBCP 방식으로 일반게시판 만들기

 

일반게시판 : 주요 기능 소개

1. Connection Pool

2. 액션태그

3. DTO, DAO 클래스

4. 페이징 처리 ( inline View )

- SQL 문 상에 서브쿼리 형태로 만드는 인라인뷰

- 페이징 처리가 주요 기능이다

 

일반게시판 : 프로젝트 생성

- 이렇게 구조를 만들기 위해

- board 라는 프로젝트부터 생성

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

 

일반게시판 : 기초 파일 가져오기

- 클라우드에서 게시판 글 작성 양식, 환경설정 코드가 있는 board 폴더를 가져옴

 

일반게시판 : 몇가지 환경 구축

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

- 커넥션 풀의 환경설정 파일이다

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

 

일반게시판 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

- dbcpAPITest.jsp

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

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

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

 

 

일반게시판 : 테이블 및 시퀀스 생성

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

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

create table board0( 
    num number primary key,
    writer varchar2(20) not null,
    email varchar2(30),
    subject varchar2(50) not null,
    passwd varchar2(20) not null,
    reg_date timestamp not null,
    readcount number default 0,
    content varchar2(2000) not null,
    ip varchar2(20) not null );

create sequence board0_seq
    start with 1
    increment by 1
    nocache;
- num : 화면에 출력되는 번호가 아니라, 내부적으로만 있는 번호
- passwd : 글을 수정/삭제 시 비번이 맞는 경우에만 삭제되도록 하기위함
- readcount : 조회수이므로 number 타입, 디폴트는 0
- ip : 글을 작성한 사용자의  ip 값이 저장됨
- 시퀀스는 num 이라는 기본키 컬럼에 들어갈 시퀀스이다

 

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

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

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

- 시퀀스는 num 이라는 기본키 컬럼에 들어갈 시퀀스이다

- 테이블과 시퀀스 생성 확인

- 생성확인 완료

 

일반게시판 : DAO 와 DTO 클래스 만들기

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

- 지금은 같은 패키지인 board 안에 넣자

 

일반게시판 : DTO 클래스 작성

- 이걸 가져와서 DTO 클래스에 복붙

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

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

+ java.sql 의 Timestamp import

- getter / setter 메소드 추가

 

- DTO 클래스 BoardDataBean.java 완성 코드

// DTO (Data Transfer Object)
package board;

import java.sql.Timestamp;

public class BoardDataBean {
	
	private int num;		// 프로퍼티(property)
	private String writer;
	private String email;
	private String subject;
	private String passwd;
	private Timestamp reg_date;
	private int readcount;
	private String content;
	private String ip;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public Timestamp getReg_date() {
		return reg_date;
	}
	public void setReg_date(Timestamp reg_date) {
		this.reg_date = reg_date;
	}
	public int getReadcount() {
		return readcount;
	}
	public void setReadcount(int readcount) {
		this.readcount = readcount;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
}

 

일반게시판 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2. 정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 일반게시판 CRUD 관련 메소드

 

1. 싱글톤

- 외부 접근 불가능하게 private, 공유하기 위해 static 해서 자기자신의 클래스로 객체 생성을 한번만 하기

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- DBCP 방식으로 DB 와 연결하므로 이 DB 연결시 커넥션을 구해오는 이 메소드를 사용해야한다

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 dbcpAPITest.jsp 3줄을 복사해서 넣는다

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

 

4. 그 이후 일반게시판 CRUD 관련 메소드

- 가장 먼저 글작성 메소드를 만들어야한다, 그 후 다양한 메소드들을 DAO에 작성

 

일반게시판 : 파일별 기능 및 구조 설명

- 만들어야할 파일들 보기

+ check.jsp 는 유효성 검사

- 글작성 메소드인 writeForm.jsp 먼저 작성

 

일반게시판 : 글 작성 기능

- writeForm.jsp 에서 글 작성시 write.jsp 로 넘어감

- writeForm.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%@ include file="color.jsp"%>

<html>
<head>
	<title>게시판</title>
	<link href="style.css" rel="stylesheet" type="text/css">
	<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
	<script src="check.js"></script>
</head>   

<body bgcolor="<%=bodyback_c%>">  
<center><b>글쓰기</b>
<br>
<form method="post" name="writeform" action="writePro.jsp">

<table width="430" border="1" cellspacing="0" cellpadding="0"  bgcolor="<%=bodyback_c%>" align="center">
   <tr>
    <td align="right" colspan="2" bgcolor="<%=value_c%>">
	    <a href="list.jsp"> 글목록</a> 
   </td>
   </tr>
   <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">이 름</td>
    <td  width="330">
       <input type="text" size="10" maxlength="10" id="writer" name="writer"></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >제 목</td>
    <td  width="330">    
       <input type="text" size="40" maxlength="50" id="subject" name="subject"></td>	
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center">Email</td>
    <td  width="330">
       <input type="text" size="40" maxlength="30" id="email" name="email" ></td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >내 용</td>
    <td  width="330" >
     <textarea id="content" name="content" rows="13" cols="40"></textarea> </td>
  </tr>
  <tr>
    <td  width="100"  bgcolor="<%=value_c%>" align="center" >비밀번호</td>
    <td  width="330" >
     <input type="password" size="8" maxlength="12" id="passwd" name="passwd"> 
	 </td>
  </tr>
<tr>      
 <td colspan=2 bgcolor="<%=value_c%>" align="center"> 
  <input type="submit" value="글쓰기" >  
  <input type="reset" value="다시작성">
  <input type="button" value="목록보기" OnClick="window.location='list.jsp'">
</td></tr></table>    
   
</form>      
</body>
</html>

 

- 여기서 입력한 값을 writePro.jsp 로 전송한다

- wirtePro.jsp 생성 후 작성하자

- writePro.jsp

<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
    request.setCharacterEncoding("utf-8");
%>

<jsp:useBean id="board" class="board.BoardDataBean"></jsp:useBean>
<jsp:setProperty property="*" name="board"/>

<%
	// 글을 작성한 사용자의 IP주소를 구해오는 메소드
	String ip = request.getRemoteAddr();
	board.setIp(ip);
	
	BoardDBBean dao = BoardDBBean.getInstance();
	int result = dao.insert(board);	// insert SQL문 실행
	
	if(result == 1) {
%>
	<script>
		alert("글 작성 성공");
		location.href  = "list.jsp";
	</script>
<%	} else {%>
	<script>
		alert("글 작성 실패");
		history.go(-1);
	</script>
<%	}%>

0. 화면 출력을 할때만 HTML 코드가 필요하다, 여기선 출력을 하지 않으므로 위의 2줄 제외하고 HTML 코드를 지운다

1. writeForm.html 에서 한글값이 post 로 넘어오므로 한글 인코딩

2. 폼에서 넘어온 값을 DTO 객체에 저장해야하므로 DTO 객체를 생성하고 각 프로퍼티에 값 저장

- useBean action tag 의 class 속성에는 패키지부터 시작해서 클래스까지 적어줌

- setProperty action tag 의 property 속성에는 "*", id 속성에는 객체명을 써야하므로 board 를 쓴다

- DTO 객체에 저장 = 폼에서 넘어온 값을 힙메모리상에 저장

3. 폼에서 넘어가지 않는 값들은 setProperty action tag 에 의해 DTO 객체에 값이 저장되지 않음, 이들은 다른 방법으로 값 설정해야함

- writer, email, subject, passwd, content 는 폼에서 넘어와서 서버측 메모리 (DTO 객체) 에 저장된다

- num, reg_date, readcount, ip 는 폼에서 넘어오지 않으므로 DTO 에 저장되지 않았음

- 대신, 필드에 있는 int 형 변수는 자동으로 0 으로 초기화 되므로 num, readcount 는 0 이 됨

- num 값은 sequence 로 값을 입력, reg_date 는 sysdate 로 값을 입력, readcount 는 기본값 0, ip 는 request.getRemoteAddr() 로 클라이언트의 ip를 구해서 직접 집어넣기

- ip 프로퍼티는 이렇게 값을 설정한다

- 나머지는 오라클에 insert 할때 값 넣기

4. 사용자가 글 작성시 입력한 것들과 정보를 DB 에 insert 하기위한 DAO 의 메소드 insert() 생성

- 서버측 메모리(DTO 객체) 에 저장된 데이터를 DB 에 저장

- DAO 객체 생성 선행

5. 삽입 성공시 1을 리턴받게해서 if문을 통해 list.jsp (목록페이지) 로 가게함

6. 삽입 실패시 다시 글 작성 폼으로 돌아가게 함

 

- DAO 클래스로 가서 글작성 메소드인 insert() 를 작성하자

 

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

- insert() 메소드 작성

- BoardDBBean.java

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
}

<insert() 메소드 형식 작성>

- 삽입 성공시 1을 반환하도록 하기 위해 리턴자료형은 int

- 삽입할 데이터를 매개변수로 가져와야하므로 매개변수 자료형은 DTO

<insert() 메소드 내용 작성>

1. Connection, PreparedStatement 객체 생성

2. try-catch-finally 문을 만들고 try 안에서 getConnection() 으로 커넥션 풀에서 커넥션을 구해옴

3. insert SQL 문에 num 컬럼은 sequence 로, reg_date 컬럼은 sysdate 로 값을 채우기, 나머지는 ? 로 채우기

- board0_seq.nextval 을 num 컬럼 자리에 넣기

- ip 는 wiretPro.jsp 에서 값을 DTO 객체에 넣었었다

4. Connection 객체 con 으로 작성한 SQL 문을 읽어서 PreparedStatement 객체 생성

5. ? 에 값을 세팅할때 자료형에 따라 메소드가 다름을 주의

- number 형은 setInt, varchar2 형은 setString

- readcount 컬럼은 board.getReadcount() 로 할당해도되고 0 으로 직접 설정해도 된다

- readcount 프로퍼티가 자동으로 0 으로 초기화되었기때문에 board.getReadcount() 는 0 이다

6. insert sql 문을 executeUpdate() 메소드로 실행

- insert sql 문이 잘 실행되면 1 을 리턴

7. PreparedStatement 객체, Connection 객체 닫기

 

+ DAO 클래스 아직 미완성, 더 추가할 것

 

- 추가한 코드 부분만

 

- sql 파일에서 select 로 검색해보면 성공적으로 삽입 되었음을 확인 가능하다

- 이제 list.jsp (목록 페이지) 를 생성하자

 

일반게시판 : 게시판 목록 페이지 (페이징 처리)

- 페이징 처리를 잘 이해하며 구현하기 위해서 우선 강제로 101개 이상의 데이터를 board.sql 파일에서 강제로 삽입하자

- Alt + X 로 여러번 실행해서 101 개 이상의 데이터를 입력하자

- 130 개의 데이터가 board0 테이블에 삽입된 상태이다

 

- list.jsp 파일 생성 후 작성

- list.jsp (미완성, 이후 추가)

<%@page import="board.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>

<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호가 저장되는 변수
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 출력할 데이터의 시작번호
	// endRow : 각 page에 출력할 데이터의 끝번호
	// page = 1 : startRow=1, endRow=10
	// page = 2 : startRow=11, endRow=20
	// page = 3 : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		// 게시판 목록
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list : " + list);
%>

</body>
</html>

- 코드는 아래에서 설명

 

페이징 처리를 위해서 만들어야하는 변수

- 기본변수를 3개 만들어야함

- 연산에 의해 기본변수로부터 파생된 변수를 5 ~ 6 개 만들어야함

- 총 8 ~ 9 개 변수를 만들어야 페이징 처리 가능

 

기본 변수 3가지

	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호가 저장되는 변수
	int currentPage = Integer.parseInt(pageNum);

1. int page_size : 한 화면에 몇개의 데이터를 출력할지 정의하기 위한 기본 변수

- 현재는 10 으로 지정

- 한 페이지에 보이는 글의 개수를 의미

+ String pageNum : 'page' 란 변수를 통해 넘어오는 값을 저장

2. int currentPage : 현재 페이지 번호를 저장하는 기본 변수

- 문자형 변수인 pageNum 을 int 형으로 변환한 값을 저장하는 변수

- 사용자가 '1' 을 클릭시 현페이지인 list.jsp 로 링크를 걸고 get 방식으로 값을 전달, list.jsp?page=1 로 값 1을 전달한다

- list.jsp 에서 list.jsp 를 링크거는것임, 다시 list.jsp 가 로딩됨

- 그럼 그 page 란 변수에 저장된 1 을 저장하는 변수가 String pageNum 변수

 

pageNum 이 null 값인 경우

1) 지금처럼 list.jsp 를 그냥 실행했을 경우,

2) 글 작성에 성공하고 list.jsp 로 가는 경우이다

- pageNum 이 null 일땐 1 페이지를 출력

+ 1 page 는 가장 최근글이 나오는 곳

 

- 다음은 이 기본변수들로 파생된 변수 만들기

 

currentPage 파생 변수

1) int startRow : 각 page에 출력할 데이터의 시작번호
2) int endRow : 각 page에 출력할 데이터의 끝번호
ex) page = 1 : startRow=1, endRow=10 // 사용자가 1 페이지 선택시
ex) page = 2 : startRow=11, endRow=20 // 사용자가 2 페이지 선택시
ex) page = 3 : startRow=21, endRow=30 // 사용자가 3 페이지 선택시

	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;

- 잘라주기 위한 첫번째 번호가 startRow, 마지막 번호가 endRow

- rownum 자리에 넣을 컬럼이 startRow, endRow

- currentPage 에 의해 좌우되므로 사용자가 몇번 페이지를 눌렀냐에 따라 starRow, endRow 값이 달라짐

 

3. int count : DB 에서 그룹함수로 구하는 총 데이터 갯수를 저장하는 기본 변수

- 이제 DAO 클래스로 가서 데이터의 총 개수를 반환하는 getCount() 메소드를 생성하자

 

DAO 클래스 데이터 총 개수 반환 메소드

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
}

- DB 와 연동해서 그룹함수 count(*) 로 데이터의 총 개수를 가져오기

- 결과를 리턴할 ResultSet 객체 rs 만들기

 

- 추가한 부분 코드만

- 실행시 콘솔창에 총 데이터 개수가 출력된다

 

- 다시 list.jsp 로 돌아가서 List 를 만들자

- count 가 0 보다 크면 = 값이 있으면

- DAO 클래스의 getList 메소드로 리스트를 자를 것

- 잘라주기 위한 startRow, endRow 를 전달 ex) 사용자가 1 페이지 클릭시 1, 10 전달

- getList() 는 자른 후 잘라진 리스트를 반한다

 

- DAO 클래스에서 getList() 메소드 만들기


DAO 클래스 특정 인덱스 사이 데이터만 추출한 목록 구하기

- getList() 메소드 작성

- BoardDBBean.java (추가)

// DAO (Data Access Object)
package board;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

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

public class BoardDBBean {
	// 싱글톤 : 객체 생성을 한번만 수행하는 것
	private static BoardDBBean instance = new BoardDBBean();
	
	public static BoardDBBean 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();
	}
	
	// 글 작성
	public int insert(BoardDataBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			String sql = "insert into board0 ";
			sql += "values(board0_seq.nextval,?,?,?,?,sysdate,?,?,?)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getWriter());
			pstmt.setString(2, board.getEmail());
			pstmt.setString(3, board.getSubject());
			pstmt.setString(4, board.getPasswd());
			pstmt.setInt(5, board.getReadcount());	// 0
			pstmt.setString(6, board.getContent());		
			pstmt.setString(7, board.getIp());
			
			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;
	}
	
	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			String sql = "select count(*) from board0";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			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;
	}
	// 게시판 목록 구하기 : 데이터 10개 추출
	public List<BoardDataBean> getList(int start, int end) {
		List<BoardDataBean> list = new ArrayList<BoardDataBean>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from (select rownum rnum, board.* from ";
			sql += " (select * from board0 order by num desc) 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()) {
				BoardDataBean board = new BoardDataBean();
				
				board.setNum(rs.getInt("num"));
				board.setWriter(rs.getString("writer"));
				board.setEmail(rs.getString("email"));
				board.setSubject(rs.getString("subject"));
				board.setPasswd(rs.getString("passwd"));
				board.setReg_date(rs.getTimestamp("reg_date"));
				board.setReadcount(rs.getInt("readcount"));
				board.setContent(rs.getString("content"));
				board.setIp(rs.getString("ip"));
				
				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;
	}
}

- 중간에 있는 데이터 10개를 추출하기 위해 서브쿼리 형태의 인라인 뷰 사용

- 사용자가 1 선택시 1, 10 이 매개변수로 넘어오고 그럼 1 ~ 10 사이의 데이터를 DB 로부터 가져와서 rs 에 저장

- 그 rs 에서 데이터를 하나씩 가져와서 DTO 객체 안에 저장하고 그걸 list 에 add 해서 추가

- 그럼 1 ~ 10 까지의 데이터가 list 안에 저장되고 그게 리턴된다

- 각각의 글이 저장된 각 DTO 객체가 list 안에 저장되었으므로, 각 데이터가 있는 위치의 주솟값이 콘솔창에 출력된다

- 현재는 page_size 를 10으로 설정했으므로 10개가 출력되고 있다

+ DTO 객체는 데이터가 저장된 위치를 가리키는 주소값

 

- 추가된 부분 코드만 캡처

 

- 그럼 이 메소드를 호출한 list.jsp 에서는 1 ~ 10 까지의 데이터만을 저장한 리스트를 돌려받음

 

+ 현재 getList() 로 가져온 글

- list.jsp 에서 이렇게 출력시 129 가 나온다

- 현재는 그냥 list.jsp 를 실행만 하므로 pageNum 은 "1", currentPage 는 1 이므로 1 ~ 10 까지의 데이터를 DB 에서 가져왔다

- get(1) 은 두번째로 최근에 입력된 글이므로 list.get(1).getNum() 은 129 가 나옴

- get(0) 은 가장 최근에 입력된 글이므로 list.get(0).getNum() 은 130 이 나온다

- 즉 가장 최근 글 부터 10번째 최근 글까지를 저장한 list 를 getList() 메소드를 통해 가져온 것

 

- 이렇게 getList() 로 가져온 리스트를 아래에 출력하자

- list.jsp (추가)

<%@page import="board.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>

<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호가 저장되는 변수
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 출력할 데이터의 시작번호
	// endRow : 각 page에 출력할 데이터의 끝번호
	// page = 1 : startRow=1, endRow=10
	// page = 2 : startRow=11, endRow=20
	// page = 3 : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		// 게시판 목록
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list : " + list);
/* 	System.out.println(list.get(1).getNum()); */

	if(count == 0) {
%>
		작성된 글이 없습니다.
<% 	} else {	%>
		<a href="writeForm.jsp">글작성</a>  글갯수 : <%=count %>
		
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<td>번호</td>
				<td>제목</td>
				<td>작성자</td>
				<td>작성일</td>
				<td>조회수</td>
				<td>IP주소</td>
			</tr>
<%
			for(int i = 0 ; i < list.size() ; i++) {
				BoardDataBean board = list.get(i);
%>				
				<tr>
					<td></td>
					<td><%=board.getSubject() %></td>
					<td><%=board.getWriter() %></td>
					<td><%=board.getReg_date() %></td>
					<td><%=board.getReadcount() %></td>
					<td><%=board.getIp() %></td>
				</tr>
<%
			}	// for end
%>
		</table>
<%	}%>



</body>
</html>

- 번호값은 내부에서 쓰는 값과 화면에 출력되는 번호는 다른 값을 쓸 것이므로 출력하지 않았다

- list.jsp 수정할 부분

1. 번호값은 화면출력 번호로 따로 설정

2. 날짜 시간 포맷을 수정하자

 

num 값 vs 화면 출력 번호

- num 값은 내부에서 쓰는 번호로서, 중간에 데이터가 지워져도 건너뛴다

- 화면 출력 변호값은 중간에 데이터가 지워지면 번호가 땡겨진다

 

- list.jsp (수정 완료, 미완성, 이후 추가)

<%@page import="java.text.SimpleDateFormat"%>
<%@page import="board.BoardDataBean"%>
<%@page import="java.util.List"%>
<%@page import="board.BoardDBBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>

<%
	// 1. 한 화면에 출력할 데이터 갯수
	int page_size = 10;

	String pageNum = request.getParameter("page");
	if(pageNum == null) {
		pageNum = "1";	// 1 page : 최근글이 보이는 페이지
	}
	
	// 2. 현재 페이지 번호가 저장되는 변수
	int currentPage = Integer.parseInt(pageNum);
	
	// startRow : 각 page에 출력할 데이터의 시작번호
	// endRow : 각 page에 출력할 데이터의 끝번호
	// page = 1 : startRow=1, endRow=10
	// page = 2 : startRow=11, endRow=20
	// page = 3 : startRow=21, endRow=30
	int startRow = (currentPage - 1) * page_size + 1;
	int endRow = currentPage * page_size;
	
	// 3. 총 데이터 갯수
	int count = 0;
	
	BoardDBBean dao = BoardDBBean.getInstance();
	count = dao.getCount();
	System.out.println("count : " + count);
	
	List<BoardDataBean> list = null;
	if(count > 0) {
		// 게시판 목록
		list = dao.getList(startRow, endRow);
	}
	System.out.println("list : " + list);
/* 	System.out.println(list.get(1).getNum()); */

	if(count == 0) {
%>
		작성된 글이 없습니다.
<% 	} else {	%>
		<a href="writeForm.jsp">글작성</a>  글갯수 : <%=count %>
		
		<table border=1 width=700 align=center>
			<caption>게시판 목록</caption>
			<tr>
				<td>번호</td>
				<td>제목</td>
				<td>작성자</td>
				<td>작성일</td>
				<td>조회수</td>
				<td>IP주소</td>
			</tr>
<%
			SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage - 1) * page_size;

			for(int i = 0 ; i < list.size() ; i++) {
				BoardDataBean board = list.get(i);
%>				
				<tr>
					<td><%=number-- %></td>
					<td><%=board.getSubject() %></td>
					<td><%=board.getWriter() %></td>
					<td><%=sd.format(board.getReg_date()) %></td>
					<td><%=board.getReg_date() %></td>
					<td><%=board.getReadcount() %></td>
					<td><%=board.getIp() %></td>
				</tr>
<%			

			
			}	// for end
%>
		</table>
<%	}%>



</body>
</html>

- 추가된 코드에 대한 설명은 아래

 

1. 번호값은 화면출력 번호로 따로 설정

			// number : 각 페이지에 출력될 시작 번호
			int number = count - (currentPage - 1) * page_size;

- number 변수는 각 페이지에 출력될 시작 번호를 구해서 저장한다

- 데이터가 130개일때, 1 페이지를 클릭시 number 는 130 이 되고, 2 페이지를 클릭시 number 는 120 이 된다

- 그리고 list 에서 가져온 데이터를 하나씩 출력하기위한 for문 안에서 이렇게 감소시켜준다

 

2. 등록 날짜 포맷

- for 문 위쪽 바깥에서 SimpleDateFormat 객체 생성

- 그리고 list 에서 가져온 데이터를 하나씩 출력하기위한 for문 안에서 이렇게 등록날짜를 출력시켜준다

 

- 결과 캡처

- URL 상에 ?page=2 를 추가한 뒤 실행하면 두번째 페이지가 나타나면서 120 이 number 가 되고 다른 것들은 전부 -- 로 인해 구해짐

+ Recent posts