Spring3 + Mybatis3 기본설정정리 Spring Framework

1. 사용기술 및 툴 목록

- JAVA SDK 1.5 이상
- Eclipse Java EE IDE for Web Developers(Indigo SR1)
- Spring Framework 3.x.x
- mybatis 3.0.6(mybatis 3.1.0 버전에서 연동시 몇가지 문제가 발생)
- mybatis-spring 1.x.x
- Apache Commons DBCP
- JUnit4
- quartz 1.8.5(스프링연동시 호환성 문제로 최신버전제외)
- log4j 1.2.16
- json-lib

2. 이클립스 설정 및 사용하는 라이브러리 설정

이클립스 사이트(http://www.eclipse.org/)에서 Eclipse IDE for JAVA EE Developer 버전을 다운로드 받아 설치한다. JDK 관련 설정을 잡아준다.

File > New > Dynamic Web Project 를 선택해 웹 프로젝트를 생성한다. Target runtime은 Apache Tomcat v7.0 이상 web module version은 3.0 이상으로 설정한다.

Spring Framework 사이트(http://www.springsource.org/spring-framework#download)에서 Spring Framework 배포본을 다운로드 받는다. 다운로드 받은 압축 파일의 압축을 해제하고 dist 디렉토리 이하의 jar 파일들을 이클립스의 lib 디렉토리로 복사한다.

mybatis 사이트(http://code.google.com/p/mybatis/)에서 mybatis배포본을 다운로드 받는다. 다운로드하는 버전은 3.0.6버전으로 한다. 압축해제한 디렉토리에서 mybatis jar파일과 optional 디렉토리의 jar 파일들을 이클립스의 lib 디렉토리에 복사한다.

mybatis-spring 사이트(http://code.google.com/p/mybatis/wiki/Spring)에서 mybatis-spring 배포본을 다운로드 받는다. 압축해제한 디렉토리에서 mybatis-spring jar와 lib디렉토리의 aopaliance jar를 이클립스의 lib 디렉토리로 복사한다.

JSTL 구현 사이트(http://jstl.java.net/download.html)에서 JSTL 구현체를 다운로드 받는다. 다운로드 받은 jar 파일을 이클립스의 lib 디렉토리로 복사한다.

각각의 프로젝트 환경에 맞는 JDBC 드라이버를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다. MSSQL(http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=21599), MYSQL(http://www.mysql.com/products/connector/), Oracle(http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html)

JSON-Lib 사이트(http://sourceforge.net/projects/json-lib/files/)에서 라이브러리를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다. JSON-lib의 의존성문제를 해결하기 위해서, commons-lang(http://commons.apache.org/lang/download_lang.cgi), commons-beanutils(http://commons.apache.org/beanutils/download_beanutils.cgi), commons-collections(http://commons.apache.org/collections/download_collections.cgi), commons-logging(http://commons.apache.org/logging/download_logging.cgi), ezmorph(http://sourceforge.net/projects/ezmorph/files/) 라이브러리를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다.

Apache Commons DBCP 사이트(http://commons.apache.org/dbcp/download_dbcp.cgi)에서 라이브러리를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다. DBCP 라이브러리의 의존성 문제를 해결하기 위해서 commons-pool(http://commons.apache.org/pool/download_pool.cgi) 라이브러리를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다.

Quartz 사이트(http://quartz-scheduler.org/downloads/catalog)에서 라이브러리를 다운로드 받아 이클립스의 lib 디렉토리에 복사한다.

3. 설정파일 구축

[/WebContent/WEB-INF/web.xml]


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- Spring에서 사용하는 빈 설정이 위치하는 XML파일 -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/config/root-context.xml</param-value>
 </context-param>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
  
 <!-- 요청시 입력받는 파라미터의 한글처리를 위하여 인코딩을 UTF-8로 고정 -->
 <filter>
   <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>UTF-8</param-value>
  </init-param>
 </filter>
 <filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/-</url-pattern>
 </filter-mapping>

 <!-- 일반적인 정적인 파일은 Spring을 통하지 않도록 설정함. 추가적인 확장자가 필요하면 여기에 등록 -->
 <servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.js</url-pattern>
  <url-pattern>*.gif</url-pattern>
  <url-pattern>*.jpg</url-pattern>
  <url-pattern>*.png</url-pattern>
  <url-pattern>*.css</url-pattern>
  <url-pattern>*.ico</url-pattern>
 </servlet-mapping>

 <!-- DispatcherServlet 설정. 위에서 설정한 확장자 이외에는 Spring의 DispatcherServlet을 통한다. -->
 <servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/config/servlet-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
  
 <servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 
 <!-- 파일을 찾을 수 없을때의 설정 -->
 <error-page>
  <error-code>404</error-code>
  <location>/errors/404</location>
 </error-page>

</web-app>

[/WebContent/WEB-INF/config/servlet-context.xml]

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

 <!-- Spring MVC 애노테이션 설정을 활성화한다.(@Controller 등의) -->
 <annotation-driven />

 <!-- HTTP GET 요청에서 사용할 리소스내용을 위해서 /resources 디렉토리의 파일을 가져온다. -->
 <resources mapping="/resources/-*" location="/resources/" />

 <!-- Controller에서 반환하는 view 이름의 앞뒤에 jsp 경로를 연결해준다. -->
 <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <beans:property name="prefix" value="/WEB-INF/view/" />
  <beans:property name="suffix" value=".jsp" />
 </beans:bean> 
 
 <!-- base 패키지로 설정된 패키지 이하에 포함된 @Controller등의 컴포넌트 클래스를 스캔해 자동 등록한다. -->
 <context:component-scan base-package="com.preludeb.sample" />
 
</beans:beans>


[/WebContent/WEB-INF/config/root-context.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"
 xmlns:tx="http://www.springframework.org/schema/tx
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
 <!-- 다국어 지원을 위한 설정 브라우저의 AcceptHeader를 이용하여 다국어 설정을 지원한다. -->
 <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"></bean>
 
 <!-- 리소스 디렉토리 아래의 board.properties를 MessageSource에 포함시킨다. -->
 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basename">
   <value>board</value>   
  </property>
 </bean>
 
 <!-- Spring Framework 이내에서 예외가 발생한 경우 지정한 클래스(com.preludeb.sample.core.PreludebExceptionResolver)에서 처리한다. -->
    <bean id="exceptionMapping" class="com.preludeb.sample.core.PreludebExceptionResolver"></bean>
   
    <!-- 이하의 데이터베이스 관련 빈에서 사용할 jdbc.properties를 가져오는 빈 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">    
     <property name="locations" value="classpath:jdbc.properties"/>    
     <property name="fileEncoding" value="UTF-8"/>
    </bean>
 
 <!-- Apache DBCP DataSource를 생성한다. 데이터베이스 관련 설정은 jdbc.properties를 따른다. -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClass}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
   
    <!-- mybatis SqlSessionFactory 빈을 생성한다. mybatis에서 사용할 매퍼 XML의 경로를 설정한다. -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
     <property name="dataSource" ref="dataSource"/>
     <property name="mapperLocations" value="classpath:com/preludeb/sample/mapper/-.xml"/>
    </bean>
   
    <!-- SqlSession 빈을 생성한다. -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
     <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
    </bean>
   
    <!-- 애플리케이션에서 사용할 Dao 빈을 등록한다. 각각의 Dao에 SqlSession을 주입한다. -->
    <bean id="boardDao" class="com.preludeb.sample.dao.BoardDao">
     <property name="sqlSession" ref="sqlSession"></property>
    </bean>
 
 <!-- 트랜잭션 매니저 빈을 생성한다. -->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
    </bean>
   
    <!-- @Transactional등 트랜잭션 관련 애노테이션을 활성화 한다. -->
    <tx:annotation-driven transaction-manager="transactionManager" />
   
    <!-- 스케줄링에 사용할 클래스들을 빈으로 등록한다. -->
    <bean id="scheduleTest" class="com.preludeb.sample.schedule.ScheduleTest">
     <property name="boardDao" ref="boardDao"></property>
    </bean>

 <!-- Quartz 스케줄링에서 호출할 객체와 메서드를 등록한다. concurrent를 false로 하면 해당 job이 동시에 호출되지 않는다. -->
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
     <property name="targetObject" ref="scheduleTest"></property>
     <property name="targetMethod" value="start"></property>
     <property name="concurrent" value="false"></property>
    </bean>
   
    <!-- 트리거를 등록한다. 아래 설정이면 Spring Framework가 등록된 후 10초 후에 Job이 시작되고 10초마다 Job이 반복실행된다. -->
    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
     <property name="jobDetail" ref="jobDetail"></property>     
     <!-- 10초 딜레이 -->
     <property name="startDelay" value="10000"></property>
     <!-- 10초마다 반복 -->
     <property name="repeatInterval" value="10000"></property>
    </bean>
   
    <!-- 사용할 트리거들을 등록한다. -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
   <list>
    <!-- 트리거 등록부분 임시로 주석처리 -->    
    <!-- ref bean="simpleTrigger" /-->
   </list>
  </property>
 </bean>
  
</beans>

3. 예외처리 클래스 작성

Spring Framework 내에서 발생한 예외를 처리하는 클래스를 작성한다. 클래스가 위치할 임의의 패키지에 PreludebExceptionResolver 클래스를 생성한다.

[PreludebExceptionResolver.java]

package com.preludeb.sample.core;

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

import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

public class PreludebExceptionResolver implements HandlerExceptionResolver
{  
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj, Exception exception)
    {
        ModelAndView view = new ModelAndView();       
        view.setViewName("errors/errors");
        view.addObject("exception", exception.getClass().getName());
        view.addObject("exceptionMessage", exception.getMessage());
       
        if(exception instanceof TransientDataAccessResourceException)
        {
            view.setViewName("errors/500");
        }
       
        return view;
    }   
}

4. 모델 클래스의 작성

데이터베이스 VO 등의 모델 클래스를 작성한다. 여기에서는 데이터베이스 테이블의 이름과 동일한 이름으로 모델 클래스를 작성하였다.(com.preludeb.sample.model 패키지 사용)

[Board.java]

package com.preludeb.sample.model;

import java.util.Date;

public class Board
{
    private int id;
    private String writer;
    private String subject;
    private String detail;
    private Date writeTime;
    private int count;
   
    public int getId()
    {
        return id;
    }
   
    public void setId(int id)
    {
        this.id = id;
    }
   
    public String getWriter()
    {
        return writer;
    }
   
    public void setWriter(String writer)
    {
        this.writer = writer;
    }
   
    public String getSubject()
    {
        return subject;
    }
   
    public void setSubject(String subject)
    {
        this.subject = subject;
    }
   
    public String getDetail()
    {
        return detail;
    }
   
    public void setDetail(String detail)
    {
        this.detail = detail;
    }
   
    public Date getWriteTime()
    {
        return writeTime;
    }
   
    public void setWriteTime(Date writeTime)
    {
        this.writeTime = writeTime;
    }
   
    public int getCount()
    {
        return count;
    }
   
    public void setCount(int count)
    {
        this.count = count;
    }
}

5. 데이터베이스의 준비

개발환경에 필요한 데이터베이스의 설치를 진행한다. 위에서 만든 모델클래스와 동일한 구조를 가지는 Board 테이블을 생성한다.

[Board 테이블 구조]

ID,int,자동 증가,PK
Writer,varchar(50)
Subject,varchar(200)
Detail,varchar(4000)
WriteTime,datetime
Count,int

Java Resources 아래에 resources 소스 폴더를 생성한다. 그리고 해당 폴더에 jdbc.properties 파일을 생성하고 다음처럼 DB 설정 내용을 추가한다. 다음은 MYSQL 환경에서 test 데이터베이스를 사용하는 경우의 예이다.

[jdbc.properties]

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=xxxxxxx

6. 매퍼 XML의 작성

아래의 XML처럼 SQL문이나 스토어드 프로시저를 사용하는 매퍼(com.preludeb.sample.mapper 패키지, Board.xml파일)를 작성한다. 다음의 매퍼는 MYSQL 기준으로 SQL문을 사용하거나 스토어드 프로시저를 사용하는 예제이다.

[Board.xml]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Board">
 <insert id="AddItem" parameterType="com.entaz.sample.model.Board" statementType="CALLABLE">
  {call USP_ADD_BOARD(#{writer,mode=IN,jdbcType=VARCHAR},#{subject,mode=IN,jdbcType=VARCHAR},#{detail,mode=IN,jdbcType=VARCHAR})}
 </insert>
 <select id="GetItem" parameterType="int" resultType="com.entaz.sample.model.Board" statementType="CALLABLE">
  {call USP_GET_ITEM(#{id,mode=IN,jdbcType=NUMERIC})}
 </select> 
 <select id="GetList" resultType="com.entaz.sample.model.Board">
  SELECT * FROM Board
  ORDER BY WriteTime DESC
 </select> 
 <update id="UpdateItem" parameterType="com.entaz.sample.model.Board" statementType="CALLABLE">
  UPDATE Board
  SET
  Subject = #{subject}, Detail = #{detail}
  WHERE
  ID = #{id}  
 </update>
 <update id="IncreaseCount" parameterType="int" statementType="CALLABLE">
  {call USP_INCREASE_COUNT(#{id,mode=IN,jdbcType=INTEGER})} 
 </update>
 <delete id="DeleteItem" parameterType="int" statementType="CALLABLE">
  {call USP_DELETE_ITEM(#{id,mode=IN,jdbcType=INTEGER})}
 </delete>
</mapper>

7. DAO 클래스의 작성

여기에서는 mybatis 전용의 사이트를 가정하여 별도의 인터페이스로 DAO를 분리하지 않았다. 프로젝트의 특성에 따라서 DAO 인터페이스와 이를 구현하는 Impl 클래스로 분리하여 사용하기 바란다. DAO 클래스에서는 SqlSession과 TransactionManager 객체를 주입받아 사용한다. DAO클래스는 com.preludeb.sample.dao 패키지에 작성한다.

[BoardDao.java]

package com.preludeb.sample.dao;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.preludeb.sample.model.Board;

public class BoardDao
{   
    private SqlSession sqlSession;   
    @Autowired private PlatformTransactionManager transactionManager;
   
    public void setSqlSession(SqlSession sqlSession)
    {
        this.sqlSession = sqlSession;
    }
   
    public int addItem(Board board)
    {
        return sqlSession.insert("Board.AddItem", board);
    }
   
    public Board getItem(int id)
    {
        return (Board)sqlSession.selectOne("Board.GetItem", id);       
    }
   
    public List<Board> getList()
    {
        return (List<Board>)sqlSession.selectList("Board.GetList");
    }   
   
    public int updateItem(int id, String subject, String detail)
    {
        Map<String, Object> parameters = new HashMap<String, Object>();
       
        parameters.put("id", id);
        parameters.put("subject", subject);
        parameters.put("detail", detail);
       
        return sqlSession.update("Board.UpdateItem", parameters);
    }
   
    public void increaseCount(int id)
    {  
        sqlSession.update("Board.IncreaseCount", id);       
    }
   
    public int deleteItem(int id)
    {
        return sqlSession.delete("Board.DeleteItem", id);
    }
   
    // 애노테이션 방식의 트랜잭션 처리. 여러개의 DB처리에서 오류가 발생한 경우 예외가 발생하면서 자동적으로 롤백이 수행된다.
    @Transactional
    public Board transactionAnnotation(int id)
    {
        sqlSession.update("Board.IncreaseCount", id);
        Board board = (Board)sqlSession.selectOne("Board.GetItem", id);
       
        return board;
    }   
   
    // TransactionManager를 통해서 일반적인 형식의 트랜잭션 처리를 수행한다.
    public void transcationProgrammatic(int id) throws Exception
    {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
    
        try
        {           
            sqlSession.update("Board.IncreaseCount", id);
            sqlSession.update("Board.fault", id);
            sqlSession.update("Board.IncreaseCount", id);
        }
        catch (Exception ex)
        {
            transactionManager.rollback(status);
            throw ex;
        }       
       
        transactionManager.commit(status);
    }
}

간단한 게시판을 대상으로 CRUD 기능을 구현하였다. 가장 아래 두개의 메서드는 트랜잭션 처리가 필요한 경우의 참조 코드이다.


8. 컨트롤러 클래스의 작성

사용자의 요청을 직접적으로 처리하는 컨트롤러 클래스를 작성한다. 여기에서는 간단한 비지니스 로직만 사용되므로 별도의 비지니스 로직을 담는 패키지를 생성하지 않았지만 복잡한 비지니스 로직을 사용하는 사이트라면 로직을 담당하는 패키지를 분리하여 개발하기 바란다. Board 모델에 대한 컨트롤러 클래스의 이름은 BoardController로 패키지는 com.preludeb.sample.controller로 지정하였다.

[BoardController.java]

package com.preludeb.sample.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.preludeb.sample.dao.BoardDao;
import com.preludeb.sample.model.Board;

@Controller
public class BoardController
{
    @Autowired private BoardDao boardDao;
    private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
   
    @ModelAttribute("board")
    public Board getBoardObject()
    {
        return new Board();
    }
   
    @RequestMapping(value = "/board/list", method = RequestMethod.GET)
    public ModelAndView getList()
    {
        List<Board> list = boardDao.getList();
       
        ModelAndView view = new ModelAndView();
        view.setViewName("board/list");
        view.addObject("list", list);
       
        return view;
    }
   
    @RequestMapping(value = "/board/add", method = RequestMethod.GET)
    public String addItemGet()
    {
        return "board/add";
    }
   
    @RequestMapping(value = "/board/add", method = RequestMethod.POST)
    public String addItemPost(Board board)
    {
        boardDao.addItem(board);           
       
        return "redirect:list";
    }
   
    @RequestMapping(value = "/board/detail/{id}", method = RequestMethod.GET)
    public ModelAndView getDetail(@PathVariable("id") int id)
    {  
        boardDao.increaseCount(id);
      
        ModelAndView view = new ModelAndView();
        view.setViewName("board/detail");
        view.addObject("board", boardDao.getItem(id));
       
        return view;
    }
   
    @RequestMapping(value = "/board/detailajax/{id}", method = RequestMethod.GET)
    public String getDetailAjax(@PathVariable("id") int id)
    {
        return "Board/detailajax";
    }
   
    @RequestMapping(value = "/board/detail/ajax/{id}", method = RequestMethod.GET)
    public void getDetailAjaxCall(@PathVariable("id") int id, HttpServletResponse response)
    {
        boardDao.increaseCount(id);
        Board board = boardDao.getItem(id);
       
        JSONArray json = JSONArray.fromObject(board);
       
        PrintWriter writer;
       
        try
        {
            writer = response.getWriter();
            writer.print(json);
            writer.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
   
    @RequestMapping(value = "/board/update/{id}", method = RequestMethod.GET)
    public ModelAndView updateItemGet(@PathVariable("id") int id)
    {
        ModelAndView view = new ModelAndView();
        view.setViewName("board/update");
        view.addObject("board", boardDao.getItem(id));
       
        return view;
    }
   
    @RequestMapping(value = "/board/update/{id}", method = RequestMethod.POST)
    public String updateItemPost(@PathVariable("id") int id, Board board)
    {
        boardDao.updateItem(id, board.getSubject(), board.getDetail());
       
        return "redirect:../list";
    }
   
    @RequestMapping(value = "/board/delete/{id}", method = RequestMethod.GET)
    public String deleteItem(@PathVariable("id") int id)   
    {
        boardDao.deleteItem(id);
       
        return "redirect:../list";
    }
}

간략한 컨트롤러 코드의 예이다.

9. View파일의 작성

SpringMVC에서는 View파일로 일반적인 JSP파일을 사용한다. JSP 파일은 servlet-context.xml 에서 정의하였듯이 /WebContent/WEB-INF/view 이하에 위치시킨다. 다음은 목록 부분과 AJAX를 사용한 상세페이지의 JSP 코드이다.

[/WebContent/WEB-INF/view/board/list.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Spring3 Demo</title>
</head>
<body>
<table border="1">
 <tr>
  <td><spring:message code="board.number"></spring:message></td>
  <td><spring:message code="board.subject"></spring:message></td>
  <td><spring:message code="board.writer"></spring:message></td>
  <td><spring:message code="board.writeTime"></spring:message></td>
  <td><spring:message code="board.count"></spring:message></td>
  <td><spring:message code="board.delete"></spring:message></td>
 <tr>
 <c:forEach var="board" items="${list}">
  <tr>
   <td>${board.id}</td>
   <td><a href="detail/${board.id}">${board.subject}</a>&nbsp;<a href="detailajax/${board.id}">AJAX</a></td>
   <td>${board.writer}</td>
   <td>${board.writeTime}</td>
   <td>${board.count}</td>
   <td><input type="button" value="삭제" onclick="javas-ript:document.location.href='delete/${board.id}'"></td>
  </tr>
 </c:forEach>
</table>
<input type="button" value="작성" onclick="javas-ript:document.location.href='add'">
</body>
</html>

[/WebContent/WEB-INF/view/board/detailajax.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 PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Spring3 Demo</title>
<script type="text/javascript">
 var httpRequest = null;
 
 function getXMLHttpRequest()
 {
  if(window.ActiveXObject)
  {
   try
   {
    return new ActiveXObject("Msxml2.XMLHTTP");
   }
   catch(e)
   {
    try
    {
     return new ActiveXObject("Microsoft.XMLHTTP");
    }
    catch(e)
    {
     return null;
    }
   }
  }
  else if(window.XMLHTTPRequest)
  {
    return new XMLHTTPRequest();
  }
  else
  {
   return null;
  }
 } 

 function getDetail()
 {
  var xmlHttp = getXMLHttpRequest();
  
  xmlHttp.open("GET", "../detail/ajax/${id}", false);
  xmlHttp.send(null);
  
  if(xmlHttp.status == 200)
  {
   var list = eval("(" + xmlHttp.responseText + ")");
   
   if(list[0] != null)
   {
    var board = list[0]; 
    
    document.getElementById("lblSubject").innerText = board.subject;
    document.getElementById("lblWriter").innerText = board.writer;
    document.getElementById("lblWriteTime").innerText = board.writeTime;
    document.getElementById("lblCount").innerText = board.count;
    document.getElementById("lblDetail").innerText = board.detail;
   }
  }
 }
</script>
</head>
<body -nload="javas-ript:getDetail();">
 <table border="1">
  <tr>
   <td>제목</td>
   <td><span id="lblSubject"></span></td>
  </tr>
  <tr>
   <td>글쓴이</td>
   <td><span id="lblWriter"></span></td>
  </tr>
  <tr>
   <td>글쓴시간</td>
   <td><span id="lblWriteTime"></span></td>
  </tr> 
  <tr>
   <td>조회수</td>
   <td><span id="lblCount"></span></td>
  </tr>
  <tr>
   <td colspan="2"><span id="lblDetail"></span></td>
  </tr>
  <tr>
   <td colspan="2">
    <input type="button" value="수정" onclick="javas-ript:document.location.href='../update/${id}'">
    <input type="button" value="목록" onclick="javas-ript:document.location.href='../list'">
   </td>
  </tr> 
 </table>
</body>
</html>

10. 다국어처리

root-context.xml부분에서 다국어 처리를 위한 설정을 이미 하였고 목록을 담당하는 JSP파일에서 <spring:message code="board.number"></spring:message>로 태그 형태로 다국어 리소스를 사용하였다. 다국어 리소스는 board.properties(기본), board_ko.properties(한국어), board_en.properties(영어)등의 이름으로 필요한 문화권의 숫자만큼 프로퍼티 파일을 만들어 resource 폴더 아래에 위치시킨다. 다음은 프로퍼티 파일의 예이다.

[board_ko.properties]

greeting=안녕하세요!

board.number=번호
board.subject=제목
board.writer=글쓴이
board.writeTime=작성시간
board.count=조회수
board.delete=삭제
board.write=작성

[board_en.properties]

greeting=Hello!

board.number=No
board.subject=Subject
board.writer=Writer
board.writeTime=Write Time
board.count=Count
board.delete=Delete
board.write=Write

2바이트 문자권의 프로퍼티 파일은 properties editor(http://propedit.sourceforge.jp/index_en.html)나 native2ascii 등을 통해서 인코딩 작업을 해 주어야 한다. 다국어 리소스를 코드를 통해서 처리하고 싶은 경우에는 다음의 컨트롤러 코드처럼 코딩한다.

[IndexController.java]

package com.preludeb.sample.controller;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class IndexController
{
    @Autowired
    private MessageSource messageSource;   
   
    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
   
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView index(Locale locale)
    {
        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
   
        String formattedDate = dateFormat.format(date);
       
        ModelAndView view = new ModelAndView();
        view.setViewName("index");
        view.addObject("formattedDate", formattedDate);
        view.addObject("greeting", messageSource.getMessage("greeting", null, locale));
       
        return view;
    }
}

11. Quartz를 통한 스케줄링 기능

Quartz를 통한 스케줄링 기능을 사용하기 위해서 quartz.properties 설정 파일을 작성한다. 위치는 resources 소스 폴더 이하에 위치시킨다.

[quartz.properties]

org.quartz.scheduler.instanceName=MyScheduler
org.quartz.threadPool.threadCount=3
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

그리고 다음같은 일반 클래스를 root-context.xml의 설정을 통해서 호출할 수 있다.

[ScheduleTest.java]

package com.preludeb.sample.schedule;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.preludeb.sample.controller.IndexController;
import com.preludeb.sample.dao.BoardDao;

public class ScheduleTest
{
    private BoardDao boardDao;
   
    public void setBoardDao(BoardDao boardDao)
    {
        this.boardDao = boardDao;
    }
   
    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
   
    public void start()
    {
        logger.debug("start method begin!!");
        logger.debug("start method End!!");
    }
}

스케줄링 설정은 앞에서 살펴본 root-context.xml 파일을 확인한다.


덧글

  • 박종영 2012/01/09 18:02 # 삭제 답글

    좋은 내용 감사합니다.
    간단한 테스트 환경을 구축할 내용을 찾고 있던 중 참고하였습니다.

    수고하세요~~~~
  • 프렐루드 2012/01/10 12:12 # 답글

    감사합니다. ㅎㅎ
  • 임하나 2012/05/09 15:35 # 삭제 답글

    좋은 내용 감사합니다.
    이제 첫 걸음을 띠면서 테스트를 해볼려는데 테스트를 어떻게 하는지 모르겠습니다.
    조언을 부탁드리겠습니다.
  • kakadais 2012/11/20 21:31 # 답글

    어마어마한 정리 감사드립니다.
    주석력에서 포스를 느끼고 갑니다-
댓글 입력 영역