티스토리 뷰

 지금까지는 Spring 게시판 예제를 맛보기로 구성해보며, 전체적인 구초를 한 번 훑었다. 그 마무리 단계로, 완성한 게시판 예제를 한번 더 단계별로 확인해보면서 Spring 구동의 순서와 구성 순서 등을 확인하려고 한다.

 

그럼 바로 한번 리뷰해보자.


1. 요청을 처리할 DispatcherServlet을 사용하기 위해 우선적으로 web.xml 문서에 Servlet 파일들을 등록

 

◎web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>spring_simple_board</display-name>
  
  <!-- 컨트롤러역할을 하는 서블릿의 이름 및 요청경로를 지정 -->
  <servlet>
    <servlet-name>board</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  
  <!-- 어떻게 요청이 들어왔을 때 , 처리할 것인가 -->
  <servlet-mapping>
    <servlet-name>board</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>

이때, 주의할 점은 톰캣 서버가 구동되면서 WEB-INF  => web.xml   => board-servlet.xml의 순서로 App의 정보를 읽기 때문에, 

<servlet><servlet-name>board</servlet-name>~~~ </servlet>

등록해 놓은 서블릿 이름이 board-servlet.xml 파일명과 다를 경우 HandlerMapping 불가능해 오류가 난다. 


2. board-servlet.xml 문서 작성

 

컨트롤러의 맵핑, DAO 등록 등의 역할을 수행해주는 servlet.xml 문서르 작성해야한다. 

그 방법은 이전 포스팅에서 자세하게 다루었으니 생략하겠다.

 

servlet(spring).xml 작성: https://til-choonham.tistory.com/168

 


 

3. Connection 객체 생성을 위한 Context.xml 작성

 

META-INF => Context.xml : JNDI 방식으로 데이터베이스에 접근하기 위해 만든 xml 문서

 

- JDBC 드라이버를 이용  : 접속할 때마다 Connection 객체를 생성 및 해제...

                                     따라서 동시 접속자 수가 많을 경우, 퍼포먼스 저하!!!!

 

- 스프링이 제공하는 DataSource 를 이용한 접근 방식 : 미리 Connection 객체를 여러 개 생성 후, 풀에 담아 놓고 사용

   

예) 100명이 접속할 경우, 

    JDBC 드라이버 : 100번 요청이 있으면 Connection 객체를 100번 생성.. 이후 재활용 불가능하기 때문에 DS를 사용한 다.

 DataSource : 미리 100개를 생성 . 1000명이 요청 시, 미리 생성된 100 개의 Connection 객체 get!!!

                        사용자가 일을 마치면 해당  Connection 객체를 다시 pool 에 반환..

                        자동으로 관리해줌... (Connection 객체 꺼내고 / 반환 )

 

Context.xml

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

4. DAO -> 생성자 구성

 

DAO를 구성하면서 가장 먼저 해줘야 하는 것은 3번에서 작성한 Context.xml을 맵핑하여 DS 객체로 Connection 객체를 생성하는 것이다.

 

◎BoardDAO.java

package board.dao;

/**	Connection, PreparedStatement, 쿼리 실행관련**/
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
//-------------------------------------------------------
/**	Context(Interface이다.), InitialContext 객체 **/
//	lookup(찾고자하는 이름(JNDI명)) -> 탐색기에서 검색하는 것과 같은 느낌
import javax.naming.Context;
import javax.naming.InitialContext;
//---------------------------------------------------
/**	추가 (JNDI 방식) **/
//	DataSource 객체 -> getConnection()
import javax.sql.DataSource;

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

public class BoardDAO {

	DataSource ds; 

	// 생성자 : DataSource 얻기 : InitialContext와 JNDI명
	public BoardDAO() {
		try {
			// InitialContext ctx=new InitialContext(); 이것도 가능.
			Context ctx = new InitialContext();

			// lookup("java:comp/env/찾고자하는 JNDI이름")
			ds = (DataSource) ctx.lookup("java:comp/env/jdbc/orcl");
			System.out.println("ds : " + ds);

		} catch (Exception e) {
			e.printStackTrace();
		}
	} 
   }

5. VO 구성 

 

이번 예제에서 DTO는 다음과 같이 구성되어 있다.

 

board.command => BoardCommand.java  (글쓰기 / 수정 .. 사용자 입력값 => 컨트롤러에게 전달 전용 VO)

board.dto => BoardDTO.java (데이터베이스와 컨트롤러 전달 전용 VO)

 

◎BoardCommand.java

package board.command;

//사용자로부터 순수 입력받는 값만 처리해주는 Class
public class BoardCommand {
	private String author, title, content;
	
	public BoardCommand() {
		
	}

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

◎BoardDTO.java

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

VO 역할 구분 이유 : 

 사용자 입력값은 5개 / 테이블의 필드 10 개중 사용자 입력 값은 5개, 기본값 설정이 되어 있는 필드 5개 일 경우, 기존 VO는 테이블의 전체  필드에 대한 멤버변수(private)를 선언. 그에 대한 getter /setter 가 필요

 

 만약에 동시접속자가 100,000일 경우 불필요한 5개 변수에 대한 메모리가 낭비된다. 

 

 따라서 사용자 입력값을 저장할 VO 객체와 데이터베이스의 결과 값을 저장할 VO 객체를 분리하여 사용하는 것이 합리적이다.

 


6. DAO에 각 기능에 대한 메서드 정의

 

DAO의 생성자를 제외한 메서드는 기존에 사용하던 방식과 마찬가지로 구성하면 된다. 다만, Connection 객체는 ds 맴버 변수를 이용하여 얻어야 한다.

 

◎BoardDAO.java

/* 글 목록 조회 메서드 */
public ArrayList<BoardDTO> list() {
	ArrayList<BoardDTO> list = new ArrayList<BoardDTO>();

  try {
    String sql = "SELECT * FROM springboard ORDER BY num desc";
    Connection con = ds.getConnection();
    // Connection con = pool.getConnection();
    PreparedStatement stmt = con.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);
    } // end while

  	rs.close();
  	stmt.close();
  	con.close();
  } catch (Exception e) {
  		e.printStackTrace();
  }

  return list;
}

7. 각 기능에 대한 컨트롤러 정의 & 등록

 

각 컨트롤러를 다음과 같이 정의하고,  servlet.xml에 등록해야 한다.

비지니스 로직을 처리하는 각 컨트롤러 : board.controller

 

1. 목록 처리 컨트롤러 : ListActionController  implements Controller 

 

2. 쓰기화면 처리 컨트롤러  : 스프링이 제공하는 ParameterizableViewController (화면만 응답처리할 경우)

 

@Override

public ModelAndView handleRequestInternal(~~~~) ~~~~{  }

 

handleRequestInternal( ) 메서드의 역할 : 

     viewName 속성값을 이용하여

     ModelAndView mav = new ModelAndView();  

     mav.setViewName("viewName 속성값");

     mav 객체를  반환

 

3. ParameterizableViewController 등록 시에는 반드시 viewName 속성에 출혁화면 파일명만 설정

 

◎board-servlet.xml

<!-- 1. 글 목록 보기 -->		
	<bean name="/list.do" class="board.controller.ListActionController">
	    <property name="dao">
	        <ref bean="boardDAO"/>
	    </property>
	</bean>

 

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;

//Controller를 상속받는 이유 : 요청을 받아서 처리하기 위함.
public class ListActionController implements Controller {
	
    BoardDAO dao; 	//	BoardDAO dao = new BoardDAO();
    
    public void setDao(BoardDAO dao) {
        this.dao = dao;
        System.out.println("setDao()호출됨(dao) : "+dao);
    }

	@Override
	public ModelAndView handleRequest(HttpServletRequest arg0,  HttpServletResponse arg1) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("ListActionController 실행됨!");
		
        ArrayList<BoardDTO> list = dao.list();
        ModelAndView mav = new ModelAndView();
        mav.setViewName("list"); 	//	list.jsp
        
        //request.setAttribute("list",list);
        mav.addObject("list", list); 	

        return mav;
	}

}


==============================================================

 

3. 저장 처리 컨트롤러       : WriteActionController extends AbstractCommandController

 

1. implements Controller  : 브라우저로부터 데이터가 파라미터로 전달될 경우

@Override

public ModelAndView handleRequest(HttpServletRequest arg0,  HttpServletResponse arg1)  {  

 

}

 

2. extends AbstractCommandController  : 브라우저의 입력 파라미터 명과 해당 값을 VO 객체와 자동 매핑 시킨후 

                                                       컨트롤러에게 자동으로 전할 경우,,,

@Override

protected ModelAndView handle(HttpServletRequest request, 

                                               HttpServletResponse response,             

                                               Object command,           <=== 자동 매핑된 VO 객체가 전달 되는 매개변수

                                               BindException error)

                                           

자동 매핑된 VO 객체를 Object command에 자동 전달 하려면

해당 컨트롤러를 등록할 때 미리 설정해야 한다..

 

◎board-servlet.xml

<!-- 3. 글쓰기(글 수정하기와 거의 유사) DB연결해서-->
	<bean name="/write.do" class="board.controller.WriteActionController">
	    <property name="dao">
	        <ref bean="boardDAO"/>
	    </property>
	    <property name="commandClass" value="board.command.BoardCommand"/>
	</bean>

controller) 

◎WriteActionController.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;

//	AbstractCommandController 를 상속받은 이유 : 
//	ModelAndView handle~~() 의 매개 변수가 Controller interface와 다르기 때문
/*
AbstractCommandController의 콜백메서드인 handle() 가 전달받는 객체
 handle() 콜백메서드의 매개변수와 설정 값
1. Request : 요청객체
2. Response : 응답객체
3. Object : 입력받은 값을 저장하는 객체
4. BindException : 사용자로부터 값을 입력 시, 에러가 발생하면 처리해주는 class
*/
public class WriteActionController extends AbstractCommandController {

	BoardDAO dao; // BoardDAO dao = new BoardDAO();

	public void setDao(BoardDAO dao) {
		this.dao = dao;
		System.out.println("WriteActionController setDao()호출됨(dao) : " + dao);
	}
	
	// 매개변수를 알기 쉽게 변환
	@Override
	protected ModelAndView handle(HttpServletRequest request, 
              HttpServletResponse response, 
              Object command, 
              BindException error) throws Exception {
		
		request.setCharacterEncoding("UTF-8");
		
		//	spring 방식
		//	BoardCommnad 는 상속받은 부모 class(AbstractCommandController)의 
		// "commandClass" 속성에 의해 자동 전달(주입).
        BoardCommand data = (BoardCommand)command;
        dao.write(data);
/*        
        //	기존 Model2(MVC) 방식
        String author = request.getParameter("author");
        String content =request.getParameter("content");
        String title = request.getParameter("title");
        dao.write(author, title, content);
*/
        
        

        // 기존 Model2(MVC) 방식
//        response.sendRedirect("list.jsp");
        
        //	spring 방식
		return new ModelAndView("redirect:/list.do"); 	
	}
}

/*
System.out.println("spring 방식");
//ModelAndView mav = new ModelAndView();
//mav.setViewName("list"); 		

// 또는
ModelAndView mav = new ModelAndView("list");
// 위에 ModelAndView() 안에 넣음으로서  
// mav.setViewName("list"); 생략이 가능하다.
System.out.println("spring 방식 return mav");
return mav;
//	
// 또는 
//위의 주석 문을 한줄로 처리 가능하다
*/

8. 프론트 구성

jsp파일은 기존 request 파라미터를 사용하여 구성한 것과 동일하게 구성하면 된다.


예제의 GitHub:

https://github.com/Choonham/Choonham-2020.03.10-Spring-Class-JWEB-/tree/main/spring_simple_board_complete

 

Choonham/Choonham-2020.03.10-Spring-Class-JWEB-

Contribute to Choonham/Choonham-2020.03.10-Spring-Class-JWEB- development by creating an account on GitHub.

github.com

 

Comments