구리

TIL_210610_Spring MVC 흐름 본문

카테고리 없음

TIL_210610_Spring MVC 흐름

guriguriguri 2021. 6. 11. 00:41

목차

① 스프링 MVC 흐름
② JNDI
③ DBCP
④ 스프링 MVC 관련 간단한 예시 (spring_simple_board - 글 목록, 작성,수정,삭제, 검색 구현)

- xml 문서 등록 및 작성법 

- DispatcherServlet 작성법

- 설정한 JNDI의 XML 파일로 DB에 접속하는 방법

- Controller 작성법

- 프로젝트 정리 : Controller 종류 및 용도

                               프로젝트 흐름

                               DispatcherServlet - HandlerMapping 연결 과정

 

 

스프링 MVC 흐름

구성요소 개요
Dispatcher 브라우저로부터 송신된 Request를 일괄적으로 관리 (전 프로젝트의 Servlet 느낌)
HandlerMapping RequestURL, Controller 클래스의 맵핑 관리 (전 프로젝트의 ActionFactory 느낌)
Controller 비즈니스 로직을 호출하여 처리 결과의 ModelAndView 인스턴스 반환
(전 프로젝트의 Action 느낌)
ViewResolver Controller 클래스로부터 반환된 View명을 기본으로 이동처가 되는 View 인스턴스 해결
(전 프로젝트의 url 변수들 느낌)
View 프레젠테이션층으로의 출력 데이터 설정

 

 

스프링 MVC 흐름 설명

간단하게 요약하자면 웹브라우저 =>  디스패쳐서블릿 => 컨트롤러 => 모델앤뷰 => 컨트롤러 => 디스패쳐서블릿 => 웹브라우저의 절차를 밟게 되며

화면에 출력하기 위해 View와 ViewResolver가 있다고 생각하면 됩니다.

 

①  웹브라우저에게 정보요청을 받은 디스패쳐서블릿은 어느 핸들러 매핑에게 해당 요청을 전송할지 결정 

② 디스패쳐 서블릿은 핸들러 매핑에 어느 컨트롤러를 사용할건지 물어봄. (URL로 링크)

③ 결정된 컨트롤러는 해당요청을 수행하게 됨

④ 해당요청을 처리한 컨트롤러는 디스패쳐서블릿에 결과를 보냄. 이 과정에서 Model이 생성되어 View(JSP)에서 같이 사용됨 

ModelAndView는 실제 JSP정보를 갖고 있지 않기 때문에 뷰리졸버가 실제 JSP이름으로 변환하여 해당 view를 검색함.

⑥ 검색한 결과를 View에 전송

⑦ View는 모든 과정에서 처리된 결과를 화면으로 표현함

⑧ 마지막으로 디스패쳐서블릿이 웹브라우저에 최종결과를 출력

 

(출처 : https://hunit.tistory.com/189)

 

 

JNDI

Java Naming Directory Interface의 약자로 디렉토리 서비스에서 제공하는 데이터 및 객체를 발견하고 참고(lookUp)하기 위한 자바 API입니다.

즉, 외부에 있는 객체를 가져오는 역할로 tomcat 같은 WAS를 보면 특정 폴더에 필요한 소스(라이브러리)가 있는데 그걸 사용하기 위해 JNDI를 사용합니다.

 

DBCP (DataBase Connection Pool)

DB와 연결된 커넥션을 미리 만들어 저장해두었다가 필요할 때 저장한 공간(pool)에서 꺼내쓰고 반환하는 기법으로 DB 부하를 줄이고 효율적인 자원 관리가 가능해집니다.

 

Data Source

PC에 설치된 DB에 접속하기 위한 모든 정보를 해당 Context에 하나의 이름으로 등록 후 그 정보를 객체화한 클래스로 DB에 접속하기 위한 모든 정보, Connection pool을 이용하는 Connection을 관리하는 객체의 의미로 생각하면 좋습니다.

 

 


 

 

스프링 MVC 관련 간단한 예시 (spring_simple_board - 글 목록, 작성,수정,삭제, 검색 구현)

구조 파일 생성 및 역할
src BoardDAO : JNDI명을 이용해 DB접속 및 필요 메서드 정의
META-INF Context.xml : DB정보를 태그형태로 설정
WEB-INF lib : spring 라이브러리 복사
web.xml : Dispatcher Servlet 컨트롤러 등록
board-servlet.xml : 요청 명령어 등록
WebContent 화면에 출력할 pages (~.html / ~.jsp)
page 생성 list에 있는 내용을 Web에 뿌림

 

 

프로젝트 생성 및 라이브러리 추가 

 

BoardDTOjava

더보기
package board.dto;

public class BoardDTO {
	private int num;
	private String author, title, content,date;
	private int readcnt;
	
	public BoardDTO() {
		// TODO Auto-generated constructor stub
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public String getDate() {
		return date;
	}

	public void setDate(String date) {
		this.date = date;
	}

	public int getReadcnt() {
		return readcnt;
	}

	public void setReadcnt(int readcnt) {
		this.readcnt = readcnt;
	}
	

}

web.xml

 

더보기
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>spring_simple_board</display-name>
  
  <!-- DispatcherServlet 등록 -->
  <servlet>
  	<servlet-name>board</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <!-- 스프링에서 제공하는 DspatcherServlet 을 사용할 때 board 라는 이름으로 통해 검색 한다는 의미 -->
  
  <!-- 요청 들어왔을 때 어떻게 처리할지 명시 -->
  <servlet-mapping>
  	<servlet-name>board</servlet-name>
  	<!-- 이 서블릿 명은 위  DispatcherServlet 서블릿명을 찾기위한 검색어라고 생각 -->
  	<url-pattern>*.do</url-pattern>
  	<!-- 앞에 어떤 이름이 와도 뒤에 .do로 끝나면 OK -->
  </servlet-mapping>
  
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

webxml 문서에서 오류 발생시 무조건 404 오류이며 이때 체크해야할 사항은 다음과 같습니다.

1) welcome-file-list에 있는 파일에 내가 가진 파일이 있는지 확인
2) url-pattern쪽에 요청 사항 잘 적혀 있는지 확인
3) servlet-mapping - servlet-name 서블릿명이 servlet - servlet-name와 같은지 확인
4) servlet-class 경로 및 클래스명 오류인지 확인

 

 

서블릿 등록 방법

 

<!--DispatcherServlet 등록 -->
<servlet>
	<servlet-name>서블릿명</servlet-name> //  (3) 2와 3을 비교 
	<servlet-class>등록할 서블릿 클래스 전체 경로 및 클래스명</servlet-class> (4) 3과정에서 찾으면 해당 클래스에 대한 객체 생성
</servlet>

<!--요청 들어왔을 때 어떻게 처리할지 명시 -->
<servlet-mapping>
	<servlet-name>서블릿명</servlet-name> 	  // (2) 여기서 명시하는 서블릿명은  위  DispatcherServlet 서블릿명을 찾기위한 검색어라고 생각
	<url-pattern>요청 Url 형식</url-pattern> // (1) 요청사항에 대해서는 여기로 받게됨 
</servlet-mapping>

이때 <servlet>의 서블릿명은 중복되어선 안되며 <servlet-mapping>또한 서블릿명이 중복되어선 안됩니다.

 

 

board-servlet.xml 

더보기
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 요청에 따른 컨트롤러 반환 담당 : HandlerMapping -->
	<bean id="defaultHandlerMapping"  class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
	
	<!-- DB 접속 : JNDI 방식 -->
	<bean id="boardDAO" class="board.dao.BoardDAO"></bean>
	
	<!-- 1. 글목록 보기 컨트롤러 등록 : private dao => setDao(boardDAO)-->
	<bean name="/list.do" class="board.controller.ListActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
	</bean>
	<!-- ListActionController 클래스의 dao 멤버변수에게  boardDAO 객체를 전달-->
	
	<!-- 2. 글쓰기 화면처리 컨트롤러 등록 : 스프링이 제공 -->
	<bean name="/writeui.do" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
		<property name="viewName" value="write"></property>
		<!-- ModelAndView 객체의 .setViewName("write") return ModelAndView 객체 -->
	</bean>
	
	<!-- 3. 글 저장 처리 컨트롤러 등록 -->
	<bean name="/write.do" class="board.controller.WriteActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
		<!-- BoardCommand 객체는 handle() 3번째 매개변수에 자동 전달 -->
	</bean>
	
	<!-- 4. 글 상세보기 처리 컨트롤러 등록 -->
	<bean name="/retrieve.do" class="board.controller.RetrieveActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
	</bean>
	
	<!-- 5. 글 수정 처리 컨트롤러 등록 -->
	<bean name="/update.do" class="board.controller.UpdateActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
	</bean>
	
	<!-- 6. 글 삭제 처리 컨트롤러 등록 -->
	<bean name="/delete.do" class="board.controller.DeleteActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
	</bean>
	
	<!-- 7. 조건에 따른 검색 처리 컨트롤러 등록 -->
	<bean name="/search.do" class="board.controller.SearchActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
	</bean>
	
	<!-- viewResolver(위치, 이동할 페이지 지정) -->
	<bean id="viewResolover"  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	    <property name="viewClass"   value="org.springframework.web.servlet.view.InternalResourceView"/>
	    <property name="prefix" value="/"/>
	    <property name="suffix" value=".jsp"/>
	</bean>
	
</beans>

핸들러 맵핑, 컨트롤러, 뷰리졸버를 담당하는 xml 문서로 조건은 다음과 같습니다.

 

  ①  <beans></beans> 태그로 감싸져야합니다.

  ② 작업에 필요한 객체 생성 및 클래스 등록해야 합니다.

   

<beans>
	<bean>
    	<!-- 작업에 필요한 객체 생성 및 클래스 등록 -->
	</bean>
</beans>

  ③ 첫번째 등록<bean> 태그는 반드시 HandlerMapping 이어야 합니다.

  ④ ViewResolver도 반드시 등록해야 합니다.

  ⑤ 그 외, Controller는 반드시 해당 클래스 생성 후 xml 문서에 등록해야 합니다.

 

 

클래스 등록 방법

1) 해당 xml문서에 사용할 클래스(객체) 등록 방법

<bean id="객체명" class="패키지.클래스"></bean>
ex: 만약 board.dao.BaordDAO 가 존재할 경우
<bean id="bDAO" class="board.dao.BaordDAO"></bean>

 

2) Controller 클래스 등록법 

 <bean name="요청사항(url)" class="요청사항 처리 클래스">
	<property name="해당 클래스 멤버변수명">
		<ref bean="변수에 전달할 값(객체)" />
	</property>
 </bean>

ex : 만약 board.controller.ListActionController 존재하고, 내부에 BoardDAO bDAO; 변수 존재하며 요청사항 list.do일 경우

(글 목록 같이 전달 받는 데이터가 없을 경우)

<bean name="list.do" class="board.controller.ListActionController">
	<property name="bDAO">
		<ref bean="BoardDAO" />
	</property>
       </bean>

ex : 만약 board.controller.UpdateActionController존재하고, 내부에 BoardDAO bDAO; 변수 존재하며 요청사항 update.do일 경우

(글 수정 같이 전달 받는 데이터가 있을 경우 - 사용자가 입력한 값들)

<bean name="/update.do" class="board.controller.UpdateActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
	</bean>

사용자가 입력한 값들을 BoardCommand 클래스의 객체에 저장하여 commandClass라는 변수로 전달

 

 

3) ViewResolver 클래스 등록법

<bean id="" class="스프링 제공하는 InternalResourceViewResolver">
 	<property name="viewClass" value="스프링 제공하는 InternalResourceView"></property>
	<property name="prefix" value="경로"></property>
	<property name="suffix" value="구체적 내용(확장자)"></property>
 </bean>

ex : 화면출력용 jsp 파일들이 WebContent 폴더에 존재할 경우

 <bean id="viewResolover" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 	<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"></property>
	<property name="prefix" value="/"></property>
	<property name="suffix" value=".jsp"></property>
 </bean>

 

 

DB연결

JNDI 방법으로 DB 연결하는 방법- META-INF -> Context.xml (DB정보를 태그 형태로 저장) => DB연결 정보를 Context.xml에 저장하여 DB정보를 이름으로 검색해서 가져옵니다.결과적으로, DAO에 DB에 대한 url, driver, 계정, 암호를 입력하지 않아도 됩니다.

 

 

Context.xml

더보기
<?xml version="1.0" encoding="UTF-8"?>
<Context>
	<Resource name="jdbc/orcl"
			auth="container"
			type="javax.sql.DataSource"
			username="bjy"
			password="qorwjddus96"
			driverClassName="oracle.jdbc.driver.OracleDriver"
			factory="org.apache.commons.dbcp.BasicDataSourceFactory"
			url="jdbc:oracle:thin:@localhost:1521:XE"
			maxActive="20"
			maxIdle="10">
	</Resource>
</Context>

name 속성을 찾아서 DB를 찾아가는데 사용자가 임의로 줄 수 있는데 일반적으로는 jdbc/orcl 로 사용합니다. 외부에선 name만 알면, 모든 속성을 가져올 수 있으며 이 name을 JNDI 이름이라고 합니다.

 

속성명="값" 역할
name="jdbc/orcl" JNDI이름. 이 이름으로 외부클래스(BoardDAO)에서 접근.
auth="container" 소스(DB)를 누가 관리할 것인가? (스프링 컨테이너).
type="javax.sql.DataSource" 커넥션풀에 대한 DataSource의 fullName.
해당 리소스 이용시 DataSource로 리턴
username="bjy" 접속할 계정명
password="qorwjddus96" 접속할 암호
driverClassName="oracle.jdbc.driver.OracleDriver" 접속할 데이터베이스 Driver명
factory=
"org.apache.commons.dbcp.BasicDataSourceFactory"
커넥션풀을 생성해주는 클래스 fullName
url="jdbc:oracle:thin:@localhost:1521:XE" 접속 url
maxActive="20" Connection pool이 유지하기 위해 최대 대기 Connection 수
maxIdle="10" 최대로 여분이 있는 커넥션풀 갯수 지정

 

설정한 JNDI의 XML 파일로 DB에 접속하는 방법

BoardDAO 클래스 생성자 : DataSource 얻기 (InitialContext와 JNDI명)

package board.dao;

/** 아래 클래스는 외부의 xml 문서**/
import javax.naming.InitialContext; // InitialContext implements Context
/** JNDI 방식의 Connection을 사용할 경우 : Context.xml **/
// DataSource 객체는 getConnection() 와 동일한 역할 담당 
import javax.sql.DataSource;

public class BoardDAO {
	DataSource ds;
	
	public BoardDAO() {
		// 1. Context 객체 얻기 : Context.xml 문서에서 name="jdbc/orcl" 속성값 추출
		// 방법 : InitialContext 객체 이용
		//		해당 객체가 가진 lookUp() 통해 name 속성값을 추출
		//		단, jdbc/orcl를 찾기 위해 java:comp/env/jdbc/orcl와 같이 적혀 있어야 함
		
		try {
			InitialContext ctv = new InitialContext();
			
			// Object lookup()이기에 형변환 필수  
			ds = (DataSource)ctv.lookup("java:comp/env/jdbc/orcl");
			System.out.println("ds : " + ds);
		} catch (Exception e) {
			System.err.println("BoardDAO() DataSource ERR : " + e.getMessage());
		}
	}
 }

1. Context ctx=new InitialContext(); => 객체 생성

2. Context 의 lookup("java:comp/env/찾고자하는 JNDI이름")  => JNDI 이름을 탐색하여 해당 정보를 추출.

3. BoardDAO 클래스의 멤버변수로 DataSource ds; 선언.

4. ds=(DataSource)ctx.lookup("java:comp/env/jdbc/orcl"); 로 DataSource 객체 추출.

    중요한 점은, DataSource 객체를 얻어 올 때, 자료형을 Object 객체형으로 얻어오기 때문에 DataSource 형태로 형변환 필수 !!

(System.out.println("ds : "+ds);  => 주소 값을 가져와 출력하여 테스트용으로 확인)

그후, board-servlet.xml에 요청 명령어 등록

 

BoradDAO.java

더보기
package board.dao;

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

/** 아래 클래스는 외부의 xml 문서**/
import javax.naming.InitialContext; // InitialContext implements Context
/** JNDI 방식의 Connection을 사용할 경우 : Context.xml **/
// DataSource 객체는 getConnection() 와 동일한 역할 담당 
import javax.sql.DataSource;

import board.command.BoardCommand;
import board.dto.BoardDTO;

public class BoardDAO {
	DataSource ds;
	
	public BoardDAO() {
		// 1. Context 객체 얻기 : Context.xml 문서에서 name="jdbc/orcl" 속성값 추출
		// 방법 : InitialContext 객체 이용
		//		해당 객체가 가진 lookUp() 통해 name 속성값을 추출
		//		단, jdbc/orcl를 찾기 위해 java:comp/env/jdbc/orcl와 같이 적혀 있어야 함
		
		try {
			InitialContext ctv = new InitialContext();
			
			// Object lookup()이기에 형변환 필수  
			ds = (DataSource)ctv.lookup("java:comp/env/jdbc/orcl");
			System.out.println("ds : " + ds);
		} catch (Exception e) {
			System.err.println("BoardDAO() DataSource ERR : " + e.getMessage());
		}
	}
	
	/** 글목록 조회 **/
	public ArrayList<BoardDTO> list(){
		ArrayList<BoardDTO> list=new  ArrayList<BoardDTO>();
		 
		 try {
			 String sql="select * from springboard order by num desc";
			 
			 Connection conn=ds.getConnection();
			 PreparedStatement stmt=conn.prepareStatement(sql);
			 ResultSet rs=stmt.executeQuery();
			 
			 while(rs.next()) {
				 BoardDTO data=new BoardDTO();
				 
				 data.setNum(rs.getInt("num"));
				 data.setAuthor(rs.getString("author"));
				 data.setTitle(rs.getString("title"));
				 data.setContent(rs.getString("content"));
				 data.setDate(rs.getString("writeday"));
				 data.setReadcnt(rs.getInt("readcnt"));
				 
				 list.add(data);
			 }
			 
		 }catch (Exception e) {
			System.err.println("list() ERR : " + e.getMessage() + e.getStackTrace());
		}
		 return list;
	}

	/** 새글에 대한 글번호를 위한 메서드  **/
	public int getNewNum() {
		int newNum = 1;
		
		try {
			String sql="select max(num) from springboard";
			 
			 Connection conn=ds.getConnection();
			 PreparedStatement stmt=conn.prepareStatement(sql);
			 ResultSet rs=stmt.executeQuery();
			 
			 if(rs.next()) {
				 newNum = rs.getInt(1) + 1; 
				 // max는 실제 필드명이 아니기에 index 번호 + 1 => 실제 번호
			 }
		}catch (Exception e) {
			System.err.println("getNewNum() ERR : " + e.getMessage() + e.getStackTrace());
		}
		return newNum;
	}
	
	/** 글저장 메서드 **/
	public void write(BoardCommand data) {
		try {
			int newNum = getNewNum();
			
			String sql="insert into springboard(num, author, title, content) values (?,?,?,?)";
			 
			 Connection conn=ds.getConnection();
			 PreparedStatement stmt=conn.prepareStatement(sql);
			 stmt.setInt(1, newNum);
			 stmt.setString(2, data.getAuthor());
			 stmt.setString(3, data.getTitle());
			 stmt.setString(4, data.getContent());
			
			 int n = stmt.executeUpdate();
		
		}catch (Exception e) {
			System.err.println("write() ERR : " + e.getMessage() + ", "+ e.getStackTrace());
		}
	}
	
	/** 글 상세보기 메서드 **/
	public BoardDTO retrieve(String num) {
		BoardDTO dto = new BoardDTO();
		
		try {
			// 1. 조회수 증가
			String sql="update springboard set readcnt=readcnt+1 where num=?";
			 
			Connection conn=ds.getConnection();
			PreparedStatement stmt=conn.prepareStatement(sql);
			stmt.setInt(1, Integer.parseInt(num));
			int n = stmt.executeUpdate();
			System.out.println("retrieve() 조회수 증가 결과 여부 => " + n);
			
			// 2. 전달된 글번호에 대한 글 상세 정보 반환
			stmt = null;
			sql = "select * from springboard where num=?";
			stmt = conn.prepareStatement(sql);
			stmt.setInt(1, Integer.parseInt(num));
			
			ResultSet rs = stmt.executeQuery();
			
			if(rs.next()) {
				dto.setNum(rs.getInt("num"));
				dto.setAuthor(rs.getString("author"));
				dto.setDate(rs.getString("writeday"));
				dto.setTitle(rs.getString("title"));
				dto.setReadcnt(rs.getInt("readcnt"));
				dto.setContent(rs.getString("content"));
			}
		}catch (Exception e) {
			System.err.println("retrieve() ERR : " + e.getMessage() + ", "+ e.getStackTrace());
		}
		return dto;
	}
	
	/** 글 수정 메서드 **/
	public void update(String num, BoardCommand data) {
		try {
			String sql = "update springboard set title=?, content=?, author=? where num=?";
			Connection conn=ds.getConnection();
			PreparedStatement stmt=conn.prepareStatement(sql);
			stmt.setString(1, data.getTitle());
			stmt.setString(2, data.getContent());
			stmt.setString(3, data.getAuthor());
			stmt.setInt(4, Integer.parseInt(num));
			
			int n = stmt.executeUpdate();
			System.out.println("update() 글 수정 결과 완료 여부 =>" + n);
		}catch(Exception e) {
			System.err.println("update() ERR : " + e.getMessage() + ", " + e.getStackTrace());;
		}
	}
	
	/** 글 삭제 메서드 **/
	public void delete(String num) {
		try {
			String sql = "delete from springboard where num=?";
			Connection conn=ds.getConnection();
			PreparedStatement stmt=conn.prepareStatement(sql);
			stmt.setInt(1, Integer.parseInt(num));
			int n = stmt.executeUpdate();
			System.out.println("delete() 글 수정 결과 완료 여부 =>" + n);
		}catch(Exception e) {
			System.err.println("delete() ERR : " + e.getMessage() + ", " + e.getStackTrace());;
		}
	}
	
	/** 조건에 따른 검색 메서드 **/
	public ArrayList<BoardDTO> serach(String serachValue, String searchName){
		ArrayList<BoardDTO> list = new ArrayList<BoardDTO>();
		String sql = "select * from springboard where ? LIKE '%' || ? || '%'";
		
		try {
			
			Connection conn=ds.getConnection();
			PreparedStatement stmt=conn.prepareStatement(sql);
			stmt.setString(1, searchName);
			stmt.setString(2, serachValue);
			
			ResultSet rs = stmt.executeQuery();
			
			while(rs.next()) {
				BoardDTO dto = new BoardDTO();
				dto.setNum(rs.getInt("num"));
				dto.setTitle(rs.getString("title"));
				dto.setContent(rs.getString("content"));
				dto.setAuthor(rs.getString("author"));
				dto.setDate(rs.getString("writeday"));
				dto.setReadcnt(rs.getInt("readcnt"));
				
				list.add(dto);
			}
		}catch(Exception e) {
			System.err.println("serach() ERR : " + e.getMessage() + ", " + e.getStackTrace());;
		}
		return list;
	}

}

 

<글 목록 구현>

Controller 클래스 생성 (ListActionController.java)

모든 게시글에 대한 정보를 전달

더보기
package board.controller;

import java.util.ArrayList;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import board.dao.BoardDAO;
import board.dto.BoardDTO;

public class ListActionController implements Controller {
	private BoardDAO dao;
	
	public ListActionController() {
	}

	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("setDao() 호출 : " + dao); 
	}


	/** 요청사항이 전달되면 자동 호출되는 **/
	@Override
	public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
		System.out.println("ListActionController의 handleRequest() 자동호출됨");
		
		ArrayList<BoardDTO> list = dao.list();
		ModelAndView mav = new ModelAndView();
		mav.setViewName("list");	// list.jsp를 의미
		mav.addObject("list", list);	// request.setAttribute("list", list) 개념과 동일
		
		return mav;
	}

}

 

 

해당 컨트롤러에서 멤버변수로 BoardDAO dao를 선언할 수 있는 이유는 board-servlet.xml)에서 이미 BoardDAO 객체를 생성해서 얻어왔기 때문입니다. 컨트롤러 생성 후 board-servlet.xml에 글 목록 컨트롤러를 추가합니다.

<bean name="/list.do" class="board.controller.ListActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
	</bean>

위 코드를 간단히 설명하면 ListActionController 클래스의 dao 멤버변수에게 이미 만들어진 boardDAO 객체를 전달하라는 의미인데 dao 멤버변수는 private 접근 제어자이므로 setDao() 메서드를 통해서만 dao 멤버변수에 접근할 수 있습니다. 

 

list.jsp

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.ArrayList, board.dto.BoardDTO" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>list.jsp</title>
</head>
<body>
	<table border="1">
		<tr>
			<td colspan="5">
				게시판 목록 &nbsp;&nbsp;&nbsp;
				<a href="writeui.do">글쓰기</a>
			</td>
		</tr>
		
		<tr>
			<th>번호</th><th>제목</th><th>작성자</th><th>작성일</th><th>조회수</th>
		</tr>
		
		<!-- Dispatcher  
			그중 Model에 해당하는 객체를 list.jsp의 request에게 전달
			따라서 list.jsp는 request의 attribute 영역에서 해당 객체를 추출하여 사용 가능
		 -->
		 <c:if test="${list != null }">
		 	<c:forEach items="${list }" var="dto">
		 		<tr>
				 	<td>${dto.num }</td>
				 	<td>
				 		<a href="retrieve.do?num=${dto.num }">${dto.title }</a>
				 	</td>
				 	<td>${dto.author }</td>
				 	<td>
				 		<c:set var="dto.date" value="${dto.date }" />${fn:substring(dto.date,0,10) }
				 	</td>
				 	<td>${dto.readcnt }</td>
				 </tr>	
		 	</c:forEach>
		 </c:if>
	
		<tr>
			<td colspan="5" align="center">
				<form action="search.do"> 
					<select name="searchName">
						<option value="author">작성자</option>
						<option value="title">제목</option>
					</select>
					
					<input type="text" name="searchValue"/>
					<input type="submit" value="검색" />
				</form>
			</td>
		</tr>
	</table>
</body>
</html>

list.jsp에서 글쓰기 버튼은 <a href="writeui.do">글쓰기</a>로 되어있기 때문에 board-servlet.xml 문서에서 설정만 해주면 됩니다.

(따로 DB 데이터 관련 저장, 수정, 삭제가 아닌 글쓰기 화면 페이지만 불러오면 됩니다.)

 

<bean name="/writeui.do" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
		<property name="viewName" value="write"></property>
		<!-- ModelAndView 객체의 .setViewName("write") return ModelAndView 객체 -->
	</bean>

위 코드를 보면 글쓰기 화면으로 이동시키는 컨트롤러는 ParameterizableViewController를 사용하는데 별도의 로직처리 없이 뷰페이지를 호출할 때 사용합니다.

ParameterizableViewController 클래스를 살펴보면 아래와 같은 메소드를 오버라이드 하게 되는데 해당 메서드는 viewName 속성값 이용해 ModelAndView 객체를 생성하게 되고 객체의 setViewName("viewName 속성값") 메서드를 통해 이동하게 되기에 속성값으로 파일명을 설정해야 합니다.

public ModelAndView handleRequestInternal(~~) {~~}

 

<글 쓰기 구현>

write.jsp

(글쓰기 화면을 담당하는 page)

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>write.jsp</title>
</head>
<body>
	<form action="write.do">
	<table>
		<tr>
			<td>제목</td>
			<td>
				<input type="text" name="title">
			</td>
		</tr>
		
		<tr>
			<td>작성자</td>
			<td>
				<input type="text" name="author">
			</td>
		</tr>
		
		<tr>
			<td>내용</td>
			<td>
				<textarea rows="5" cols="30" name="content"></textarea>
			</td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="작성">
			</td>
		</tr>
	</table>
	</form>
</body>
</html>

 

 

 

<write.jsp에서 실제 DB에 insert되도록 구현하는 과정 설명>

작성 순서 : xml 문서에 등록 -> DTO -> DAO -> Controller 생성 

 

BoardCommand.java

사용자로부터 순수 입력 받는 값만 처리해주는 클래스 작성

더보기
package board.command;

public class BoardCommand {
	String author, title, content;

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	

}

 

Q) 이전 프로젝트들에선 글쓰기 과정 처리시 원래 존재하는 DTO 클래스의 객체를 이용해 DB에 접근 후 처리하는데 별도의 클래스를 생성한 이유는?

기존 VO는 테이블 전체 필드에 대한 멤버변수 선언 및 그에 대한 getter/setter 메서드를 사용하였지만, 스프링 프레임 워크 자체는 대규모 프로젝트에서 많이 사용되기에 만약 동시 접속자수가 10만명일 경우 불필요한 3개의 변수에 대한 메모리가 낭비됩니다.

(BoardDTO 멤버변수 - 6개, write.jsp에서 사용자가 입력하는 값은 3개뿐이기에 나머지 변수는 필요하지 않습니다.)

따라서 사용자의 입력값을 저장할 VO객체 (BoardCommand) / DB 결과값 저장할 VO 객체(BoardDTO) 를 분리하여 사용하는 것이 효율적입니다.

 

 

board-servlet.xml에 글쓰기 컨트롤러 추가

<bean name="/write.do" class="board.controller.WriteActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
		<!-- BoardCommand 객체는 handle() 3번째 매개변수에 자동 전달 -->
	</bean>

 

 

WriteActionController.java

(글쓰기화면에서 전달된 입력 값을 테이블에 삽입하는 Controller 생성)

더보기
package board.controller;

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

import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;

import board.command.BoardCommand;
import board.dao.BoardDAO;

/** AbstractCommandController 상속받은 이유
 * 사용자가 입력한 값을 외부로부터 자동으로 전달받기 위해
 * 요청시 자동 호출되는 handle()의 매개변수의 역할
 * HttpServletRequest : request 객체
 * HttpServletResponse : response 객체
 * Object : 입력받은 값 저장하는 객체
 * BindException : 전달받을 값에 대한 오류를 예외처리
 * 
 * 따라서 이 컨트롤러 등록시, 입력받은 값들을 저장할 수 있는 객체(BoardCommand)가 필요하고,
 * 객체는 commandClass 변수를 통해 자동 전달한다**/
// 입력된 값을 DB에 저장하는 컨트롤러
public class WriteActionController extends AbstractCommandController {
	private BoardDAO dao;
	
	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("WriteActionController - setDao() : " + dao);
	}
	
	public WriteActionController() {
	}

	public WriteActionController(Class commandClass) {
		super(commandClass);
	}

	public WriteActionController(Class commandClass, String commandName) {
		super(commandClass, commandName);
	}

	@Override
	protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException error) throws Exception {
		BoardCommand data = (BoardCommand)command;
		
		// 파라미터값을 다 읽어와서 BoardDAO에 저장 후 전달했던 방식은 이제 X
		
		// BoardDAO의 write() 메서드에게 data 객체 전달
		dao.write(data);
		
		// Spring 방식 : ModelAndView
		ModelAndView mav = new ModelAndView();
		mav.setViewName("redirect:/list.do");
		return mav;
	}

}

해당 클래스는 AbstractCommandController 클래스를 상속 받는데 사용자가 입력한 값외부로부터 자동으로 전달받기 위함입니다.

해당 클래스 요청시 handle() 메소드가 자동호출되는데 매개변수의 설명은 다음과 같습니다.

 - HttpServletRequest        :    request 객체
 - HttpServletResponse     :    response 객체
 - Object                                  :    입력받은 값 저장하는 객체
 - BindException                  :    전달받을 값에 대한 오류를 예외처리

따라서 해당 컨트롤러 사용(등록)시, 입력받은 값들을 저장할 수 있는 객체 (BoardCommand)가 필요하고 객체는 commandClass 변수를 통해 자동 전달하게 됩니다.

 

글쓰기 화면 실행 결과

 

<글 상세보기 및 조회 수 증가 구현>

요청 명령어 등록 순서

1. board-servlet.xml (RetrieveActionController) 등록

2. BoardDAO -> retrieve() 작성 (위 코드에서 이미 작성했으므로 과정은 생략)

3. RetrieveActionController.java 에서 retrieve() 호출

4. retrieve.jsp 에서 BoardDTO를 출력

 

board-servlet.xml 에 글 상세 보기(RetrieveActionController) 등록

<bean name="/retrieve.do" class="board.controller.RetrieveActionController">
		<property name="dao">
			<ref bean="boardDAO" />
		</property>
	</bean>

 

글 상세보기 Controller 생성 (RetrieveActionController.java)

더보기
package board.controller;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import board.dao.BoardDAO;
import board.dto.BoardDTO;

/** 상세보기 컨트롤러 **/
public class RetrieveActionController implements Controller {
	private BoardDAO dao;
	
	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("RetrieveActionController의 setDao() 호출  : " + dao);
	}

	public RetrieveActionController() {
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("RetrieveActionController 실행됨");

		// list.jsp 제목 클릭시
		// <a href="retrive.do?num=${dto.num }">${dto.title }</a>
		String num = request.getParameter("num");
		
		// BoardDAO의 상세보기 메서드 이용하여 해당 글 데이터 조회 후 BoardDTO 객체에 담아서 리턴
		BoardDTO dto = dao.retrieve(num);
		
		// ModelAndView mav 객체 생성 후 url값 매개변수로 담아서 처리 
		// ModelAndView mav = new ModelAndView("retrieve"); 로 써도 가능
		ModelAndView mav = new ModelAndView();
		
		mav.setViewName("retrieve");
		mav.addObject("data", dto);
		
		return mav;
	}

}

 글 상세보기 컨트롤러는 Controller 인터페이스를 구현한 클래스인데 따로 사용자로부터 값을 입력 받아서 저장하는 등의 처리가 아닌 브라우저로부터 데이터가 파라미터로 전달되기 때문에 Controller  인터페이스를 구현하였습니다 (list.jsp를 보면 num이라는 파라미터 값으로 전달됩니다.)

 

 

retrieve.jsp

글의 상세 정보를 보여주는 화면

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>retrieve.jsp</title>
</head>
<body>
	<form action="update.do">
		<table>
			<tr>
				<td>번호</td>
				<td>
					<input type="text" name="num" value="${data.num }" readonly="readonly" />
				</td>
			</tr>
			
			<tr>
				<td>제목</td>
				<td>
					<input type="text" name="title" value="${data.title }" />
				</td>
			</tr>
			
			<tr>
				<td>작성자</td>
				<td>
					<input type="text" name="author" value="${data.author }" />
				</td>
			</tr>
			
			<tr>
				<td>내용</td>
				<td>
					<textarea rows="5" cols="30" name="content">${data.content }</textarea>
				</td>
			</tr>
			
			<tr>
				<td>조회수</td>
				<td>
					<input type="text" name="readcnt" value="${data.readcnt }" readonly="readonly" />
				</td>
			</tr>
			
			<tr>
				<td>날짜</td>
				<td>
					<input type="text" name="date" value="${data.date }"  readonly="readonly" />
				</td>
			</tr>
			
			<tr>
				<td>
					<input type="submit" value="수정완료" />
				</td>
				<td>
					<a href="delete.do?num=${data.num }">삭제</a>
					<a href="list.do">목록</a>
				</td>
			</tr>
		</table>
	</form>
	
</body>
</html>

 

글목록에서 특정 게시글 클릭시 글 상세정보 화면으로 이동하여 목록으로 다시 돌아가면 조회수가 증가한 것을 볼 수 있습니다.

 

<글 수정 구현>

요청 명령어 등록 순서

1. 요청 명령어 등록 : board-servlet.xml에 등록 update.do를 요청하면 UpdateActionController 가 처리할 수 있도록 환경설정

2. BoardDAO에 수정 메소드 작성 - 위 코드에 나와 있으므로 과정 생략 

3. UpdateActionController에 update() 호출하고, 처리 끝나면 list.jsp로 이동

 

board-servlet.xml 에 글 수정 컨트롤러 (UpdateActionController) 등록

<bean name="/update.do" class="board.controller.UpdateActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
	</bean>

글 상세보기 Controller 생성 (UpdateActionController.java)

더보기
package board.controller;

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

import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;

import board.command.BoardCommand;
import board.dao.BoardDAO;

public class UpdateActionController extends AbstractCommandController {
	private BoardDAO dao;
	
	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("UpdateActionController - setDao() : " + dao);
	}
	
	public UpdateActionController() {
		// TODO Auto-generated constructor stub
	}

	public UpdateActionController(Class commandClass) {
		super(commandClass);
		// TODO Auto-generated constructor stub
	}

	public UpdateActionController(Class commandClass, String commandName) {
		super(commandClass, commandName);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object command, BindException error) throws Exception {
		System.out.println("UpdateActionController의 handle() 자동호출됨");
		BoardCommand data = (BoardCommand) command; 
		String num = request.getParameter("num");
		
		dao.update(num, data);
		ModelAndView mav = new ModelAndView();
		mav.setViewName("redirect:/list.do");
		return mav;
	}

}

retrieve.jsp에서 글 수정
수정 완료 버튼 클릭시 글 수정 완료된 것을 볼 수 있습니다

 

<글 삭제 구현>

요청 명령어 등록 순서

1. board-servlet (delete.do) 등록 -> DeleteActionController 처리 구문 등록

2. BoardDAO -> delete() 메소드 작성 (위 코드에서 작성하였으므로 과정은 생략)

3. DeleteActionController.java -> delete() 메소드 호출 -> list.jsp 이동

 

board-servlet.xml 에 글 삭제 (DeleteActionController) 등록

<bean name="/delete.do" class="board.controller.DeleteActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
	</bean>

글 삭제 Controller 생성 (DeleteActionController.java)

 

더보기
package board.controller;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import board.dao.BoardDAO;

public class DeleteActionController implements Controller {
	private BoardDAO dao;
	
	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("DeleteActionController - setDao() : " + dao);
	}
	
	public DeleteActionController() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("DeleteActionController의 handleRequest() 자동호출됨");
		
		String num = request.getParameter("num");
		dao.delete(num);
		return new ModelAndView("redirect:/list.do");
	}

}

  삭제 컨트롤러는 브라우저로부터 데이터가 파라미터로 전달되기에 (<a href="delete.do?num=${data.num }">) Controller 인터페이스를 구현하였습니다. BoardDAO의 삭제 처리 메서드 호출 후 다시 list.jsp로 이동하게 됩니다.

8번 게시글 삭제 버튼 클릭시
8번 게시글 삭제 후 list.jsp로 이동

 

<글 검색 구현>

요청 명령어 등록 순서

1. board-servelt.xml 에 요청 명령어 등록 /search.do , board.controller.SearchActionController 등록

2. BoardDAO -> search() 생성 (위 코드에 나와 있으므로 과정은 생략)

3. SearchActionController.java -> search() 호출 -> 화면에 출력

 

board-servlet.xml 에 글 검색 (SearchActionController) 등록

<bean name="/search.do" class="board.controller.SearchActionController">
		<property name="dao">
			<ref bean="boardDAO"/>
		</property>
	</bean>

 

글 검색 Controller 생성 (SearchActionController.java)

더보기
package board.controller;

import java.util.ArrayList;

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

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import board.dao.BoardDAO;
import board.dto.BoardDTO;

public class SearchActionController implements Controller {
	private BoardDAO dao;
	
	public void setDao(BoardDAO dao) {
		this.dao = dao;
	}

	public SearchActionController() {
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String serachValue = request.getParameter("searchValue");
		String searchName = request.getParameter("searchName");
		
		ArrayList<BoardDTO> list = dao.serach(serachValue, searchName);
		ModelAndView mav = new ModelAndView();
		mav.addObject("list", list);
		mav.setViewName("list");
		
		return mav;
	}

}

해당 컨트롤러도 글목록 컨트롤러와 마찬가지로 저장,삭제, 수정 등의 처리를 할 필요가 없기에 Controller 인터페이스를 구현하였습니다.

list.jsp를 보면 검색 조건과 검색명을 파라미터로 전달하므로 총 2개의 값들을 request.getParameter()를 통해 전달받습니다.

 

글목록에서 특정 단어를 통해 검색
검색 결과 화면
검색 결과 화면의 url

 

<프로젝트 정리>

 Spring 컨트롤러 종류

용도 클래스(인터페이스) 용도 및 특징
단순처리 Controller  별도 기능을 제공하지 않는 컨트롤러로 요청 파라미터 처리 등의 작업을
직접 구현해줘야 합니다.

(ListActionController, RetrieveActionController,
SearchActionController에서 구현됨)
파라미터 매핑 AbstractCommandController 
(추상클래스)
요청 파라미터를 객체에 저장해주며, 컨트롤러에게 자동으로 전달할 경우 사용합니다.
(사용자로부터 값을 입력받게 되는 경우 :
로그인 폼, 글쓰기 폼, 글 수정 폼...)
(UpdateActionController, WriteActionController에서 구현됨)
정적 뷰 매핑 ParameterizableViewController  컨트롤러에서 어떤 기능도 수행하지 않고, 단순히 클라이언트의 요청을
뷰로 전달할 때 사용됩니다.

(list.jsp에서 글쓰기 버튼 클릭시 write.jsp로 이동)

 

프로젝트 총 경로 

 WEB-INF    ->  web.xml     ->    board-servlet.xml -> HandlerMapping
                                                                                               -> <bean id="boardDAO" class="board.dao.BoardDAO"></bean>
                                                                                               -> BoardDAO 생성자 -> Context.xml
                                                                                               -> list.do , write.do ,,,,
                                                                                               - > ViewResolver  
                                                  -> board-servlet.xml종료
                       -> web.xml 종료
  -> 웹 애플리케이션 구동 완료

 

 

Q) web.xml 문서에서 DispatcherServlet 생성 후 HandlerMapping이 어디 있는지 알고 실행되는 건가요?

web.xml 문서에서 명시한 <servlet-mapping> 태그의 서블릿명을 이용하여 서블릿명-serlvet.xml 문서를 찾아서 HandlerMapping을 생성하게 됩니다. 위 프로젝트에선 board로 명시했기에 board-servlet.xml 의 이름으로 문서를 생성한 것 입니다. 만약 b-servlet.xml 혹은 board.xml로 생성했을 경우 프로젝트는 오류가 발생하게 됩니다.