블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

Maven을 이용한 Jersey + Spring + MyBatis 기반의 REST 애플리케이션 개발


조대협(http://bcho.tistory.com)

빌드 스크립트 설정

개발 환경에서 가장 중요한 빌드에서 부터 알아보자, 우리가 정의하는 빌드는 다음과 같다.

"빌드란, 실행 환경에 맞춰서 소스 코드를 실행 가능한 형태의 바이너리로 변경 및 패키징 하는  일련의 과정을 정의한다."

단순하게 소스코드를 바이너리로 바꾸는 컴파일이 될 수 도 있고, 실행에 필요한 각종 리소스 (이미지, 각종 설정 파일등)을 실행 환경(서버 주소등)에 맞춰서 같이 패키징 하는 과정을 이야기 한다.

이 빌드 여러개의 연속된 작업을 포함하기 때문에, 보통 스크립트를 기반으로 수행이 되는데, C/C++의 경우에는 make 기반의 빌드 스크립트가, 자바의 경우에는 ant maven이 널리 사용된다.

 

ant vs maven

자바 기반에서 현재 가장 인기 있는 빌드 스크립트 툴은 ant maven이다. 요즘은 상당 부분이 maven으로 넘어갔다. 그렇다면 이 각각 빌드 스크립트 툴의 장점은 무엇일까?

 

유연성과 관리

ant의 경우에는 자유도가 상당히 높다. 파일을 복사하거나, 쉘 명령을 실행할 수 도 있다. 스크립트내에서 빌드, 패키징 은 물론이고 배포,테스트, 미들웨어에 대한 기동이나 정지까지 모든 것이 가능하다. 자유도가 높다는 이야기는 반대로 이야기 하면 표준화가 어렵다는 이야기가 된다.

잘 관리 하지 않으면 프로젝트 마다 또는 팀마다 빌드 스크립트가 제각각이다. 표준화가 되지 않은 빌드 스크립트는 새롭게 합류하는 개발자들에게 별도의 learning curve를 요구하게 되고, 실수를 유발한다.

또한 복잡한 형태의 빌드 프로세스를 요구 하는 개발의 경우에는 빌드 스크립트 자체를 만드는 것 자체가 복잡한 일이 된다.

 

maven은 이런 단점을 보완해서 개발되었다.

maven은 템플릿 기반으로 빌드 스크립트를 구성한다. 템플릿 기반이란, 특정 애플리케이션 타입에 대해서, 디렉토리 구조, 빌드 프로세스등이 모두 정해져있다. 그래서 애플리케이션 타입에 따라서 템플릿만 골라서 사용하게 되면 누구나 같은 디렉토리 구조에서 같은 빌드 프로세스에서 개발하게 된다. 그래서 learning curve가 상대적으로 낮고, 누구나 표준화된 환경에서 빌드가 가능하다.

반대로, 템플릿 이외의 기능에 대해서는 유연성이 떨어져서, 마음대로 무엇인가를 추가 하는 것이 어렵다. (예를 들어서 파일을 특정 클라우드에 복사한다던지). 물론 maven도 플러그인이라는 기능을 통해서 템플릿의 기능을 확장할 수 있는 기능을 제공하지만, 이 플러그인이라는 것 자체의 개발이 쉽지가 않기 때문에 템플릿의 기능을 벗어나는 순간 learning curve가 급속하게 올라간다. 다행이도 근래에는 maven에서 사용할 수 있는 플러그인들이 많이 있기 때문에, 이런 문제들은 상대적으로 줄어들고 있다.

 

의존성 관리

다음으로 라이브러리에 대한 의존성에 대해서 고민해볼 필요가 있다.

ant의 경우에는 소스코드와 라이브러리 그리고 기타 의존된 리소스 파일 (설정 파일, 스크립트, 이미지)등이 디렉토리에 있는 것을 가정하고 빌드를 진행한다. 전적으로 이러한 파일들을 챙기는 것은 개발자와 빌드 메니져의 역할인데, 이러다 보니 특히 라이브러리 관련해서 문제가 발생한다.

예를 들어 원 소스는 spring 3.0 라이브러리를 바탕으로 개발이 되었는데, 어떤 개발자는 spring 2.0을 사용해서 컴파일하고 어떤 개발자는 spring 3.1을 사용해서 컴파일 하는, 이런 일들이 발생할 수 있다는 것이다. (실제로 종종 발생하는 일)

maven의 경우 재미있는 것은 이렇게 컴파일을 하는 데 필요한 라이브러리에 대한 의존성을 정의하고, 정확한 버전을 정의하면, 컴파일 타임에 원격에 있는 repository로 부터, 명시된 버전의 라이브러리를 다운 받아서 컴파일과 패키징을 진행하기 때문에, 라이브러리의 버전 불일치가 발생될 염려가 없다. 또한 오픈소스등에서 작성한 well-known 라이브러리가 아니더라도, 자체적으로 repository 시스템을 구축하여, 팀내에서 개발한 라이브러리를 배포해놓고 사용할 수 있다. 이런 시나리오는 여러개의 모듈을 동시에 개발하는 프로젝트 팀의 경우, 모듈간의 의존 관계에서 오는 문제를 해결할 수 있는 좋은 방안이 된다.

 

근래에는 이런 장점 때문에, 유연성이 다소 적더라도 maven이 많이 사용되는 추세이다.

이제 부터 간단하게 maven 기반의 빌드 방법에 대해서 설명해보도록 한다.

여기서 소개하는 빌드 스크립트는 web application 기반의 빌드 스크립트로, 다음과 같은 시나리오를 구성할것이다.

 

-       Jersey 기반의 JSON/HTTP REST API 지원, Spring DI 기반으로 비지니스 로직 구현, MyBatis를 이용한 데이타 베이스 접근

-       빌드 환경은 개발자 PC local, 개발 환경인 dev, 스테이징 환경 stage,검증 환경인 qa 그리고 실 운영 환경이 production 환경으로 구성된다.

-       빌드 스크립트를 통해서 개발자는 빌드,배포 및 Tomcat상에서 실행 및 테스트를 진행할 수 있어야 한다.

 

maven을 이용한 Jersey + Mybatis + Spring 기반의 개발 환경 구축하기

먼저 maven 이용해서 개발을 하려면 프로젝트를 만들어야 한다. 프로젝트는 특정한 애플리케이션 타입에 맞는 템플릿을 이야기 한다. 디렉토리 구조나 빌드 프로세스들이 미리 정해져 있는 개발을 위한 하나의 비어 있는 틀이다.

 

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false  -DgroupId=spring-tutorial -DartifactId=terry -Dversion=1.0-SNAPSHOT -Dpackage=terry.spring.tutorial.ch1

 

이렇게 하면 하나의 비어 있는 프로젝트가 생성이 된다.

프로젝트가 생성이 되면, 해당 애플리케이션을 개발하는데 필요한 디렉토리 구조나 필요한 라이브러리 들이 자동으로 다운로드 되서 설치 된다. 프로젝트는 java web application 지원하는 war형태의 프로젝트이다.

maven 앞서도 설명하였듯이, 프로젝트를 생성할때, 프로젝트의 타입을 정할 있다. 여기서는 war 기반의 개발을 하기 위한 web-app 형태의 프로젝트를 생성하였는데, 프로젝트 타입은 -DarchetypeArtificatId 지정할 있다. (여기서는 가장 기본적은 maven-archetype-webapp 사용하였다.)

 

그럼 생성된 프로젝트의 모양을 살펴보자. 생성된 프로젝트의 디렉토리 구조는 다음과 같다.

 


pom.xml

ant ant.xml이나 make makefile처럼 build 대한 모든 configuration 지정한다.

 

다음으로 생성된 디렉토리를 살펴보자 src에는 *.java 소스 파일과 webapp (war root)디렉토리에 들어가는 내용들과 각종 설정 파일들이 들어간다. 상세한 내용을 살펴보자.

 

src/main/resource

디렉토리는 각종 설정 파일이 위치하는 곳이다. 디렉토리 안에 있는 파일을 class path 포함된다. war 경우에는 디렉토리에 있는 파일들은 WEB-INF/classes 디렉토리 아래에 그대로 들어간다.

예를 들어 src/main/resources/sqlmap/sqlmapper.xml 파일은 컴파일 후에, war파일내의 WEB-INF/classes/sqlmap/sqlmapper 저장된다.

 

src/main/webapp

Web resource 해당 하는 부분이다. war안에 / 디렉토리에서 부터 들어가는 html 각종 리소스 파일들을 모두 정의하낟.

 

그런데, 정작 보면 java 코드를 넣을 소스 디렉토리가 없다.

src/main/ 디렉토리 밑에 java 디렉토리를 하나 만들자.

아래 화면은 src/main/java 디렉토리를 만들고, terry.restapi.model.ContactVo.java 구현한 디렉토리 구조이다.

 

 

아래 코드는 Contact.java 클래스로, 간단하게 사용자의 이메일,이름과 전화번호를 저장하는 VO 클래스 이다.

package terry.restapi.model;

import javax.xml.bind.annotation.XmlRootElement;

 

@XmlRootElement(name="Contact")

public class ContactVo {

        String email;

        String name;

        String phone;

        public String getEmail() {

               return email;

        }

        public void setEmail(String email) {

               this.email = email;

        }

        public String getName() {

               return name;

        }

        public void setName(String name) {

               this.name = name;

        }

        public String getPhone() {

               return phone;

        }

        public void setPhone(String phone) {

               this.phone = phone;

        }

       

}

 

여기 까지 진행을 했으면, war 파일이 어떻게 만들어지는지 테스트를 해보자

% mvn package 실행하면

${HOME} 디렉토리 아래 /target/terry.war 파일이 생성된 것을 있다.

파일을 풀어보면 앞에서 만든 WEB-INF/classes/terry/restapi/model/Contact.class 패키징 되어 있는 것을 확인할 있다.

 

지금까지 간단하게 maven 프로젝트를 만들고, 소스코드를 넣고, 웹에 관련된 리소스를 정의한후에, 컴파일 war 패키징을 해보았다.

 

maven ant make 처럼 일종의 build target 제공하는데, maven에서는 이를 goal이라고 한다. pom.xml 스크립트에 따라서 다양한 goal 정할 있으나, maven에서 미리 정해져 있는 goal 중요한 goal들은 다음과 같다.

 

mvn compile : 컴파일 수행. (프로젝트내의 java 코드를 컴파일 한다.)

mvn package : jar,war,ear pom.xml에서 정해진 형태로 파일로 패키징. (컴파일을 한후, 프로젝트 내용에 따라서 패키징을 수행한다.)

mvn test : JUnit 테스트 수행.

mvn install : local repository (PC내의 디렉토리)에 패키징된 파일을 저장

mvn deploy  : remote repository (Nexus)에 패키징된 파일 저장

mvn clean : 컴파일 내용 모두 삭제

 

여기서 compile,package,clean 등은 거의 모든 빌드 스크립트에서(ant ) 공통적으로 지원하기 때문에 별도의 설명을 하지 않는다.

install deploy 대해서 살펴보자

 

앞서도 설명했듯이, maven library 대한 dependency 지정하고 스크립트를 수행하면, repository라는 곳에서 해당 라이브러리들을 읽어온다. 그러면 repository 어디일까?

mvn install 컴파일된 패키지들를 local pc 라이브러리 저장소에 배포한다. 다른 프로젝트가 라이브러리를 사용하고자 한다면, local pc 내에서 라이브러리를 찾아서 같이 컴파일 있다. 그러나 local pc에만 배포가 되었기 때문에 다른 사람은 라이브러리를 참조해서 사용할 없다. 그래서 다른 사람이 라이브러리를 참조할 있게 하려면, 네트워크 상의 공용 repository 필요하다. 이러한 공용 repository 라이브러리를 배포하는 명령이 mvn deploy이다.

spring이나 기타 라이브러리등은 각각의 공용 repository 가지고 있고, mvn 역시 이러한 repository list 기반으로 해서 라이브러리를 로딩하는데, 우리 회사나 팀에서만 사용할 있는 repository 별도로 구축하려면 어떻게 해야 할까? nexus라는 제품을 설치하면, 사내에 전용 repository 서버를 구축할 있다. (http://www.sonatype.org/nexus/)

 

maven 프로젝트 생성, 디렉토리 구조의 이해 그리고 maven 통한 컴파일 패키징에 대한 설명이 끝났다. 그러면 이제 부터, Jersey + Spring + Mybatis 개발하기 위한 설정을 해보자, 먼저 pom.xml 위의 세가지 프레임웍을 사용하기 위해서 라이브러리를 정의해야 한다.

라이브러리 정의는 <dependencies>엘리먼트 아래에 라이브러리 <dependency> 라는 엘리먼트로 정의한다.

 

<dependencies>

    <!--  jersey dependency -->

        <dependency>

            <groupId>com.sun.jersey</groupId>

            <artifactId>jersey-server</artifactId>

            <version>1.17</version>

        </dependency>

: 아래 생략

 

위의 3가지 프레임웍을 사용하기 위해서 필요한 dependency 다음과 같다.

정확하게 여기서는 JSON/REST 이용하기 위해서 Jersey 사용하고, Spring 3.1 에서 Dependency Injection만을 이용할것이며, MyBatis MySQL 사용을 위해서 MySQL JDBC Driver 사용할것이다.

아래는 지면 관계상, dependency 테이블로 정리해놨다. 글의 뒷부분의 pom.xml 전문을 따로 첨부했으니, 참고하기 바란다.

 

프레임웍 이름

groupId

artifactId

version

Jersey

com.sun.jersey

jersey-server

1.17

com.sun.jersey

jersey-servlet

1.17

com.sun.jersey

jersey-json

1.17

Spring

org.springframework

spring-core

3.1.1.RELEASE

org.springframework

spring-context

3.1.1.RELEASE

org.springframework

spring-beans

3.1.1.RELEASE

org.springframework

spring-web

3.1.1.RELEASE

org.springframework

spring-webmvc

3.1.1.RELEASE

Spring + Jersey 연결

com.sun.jersey.contribs

jersey-spring

1.17

Spring + Mybatis 연결

org.mybatis

mybatis-spring

1.0.1

MySQL JDBC

mysql

mysql-connector-java

5.1.24

 

여기서 spring+Jersey 연결에 사용되는 jsersy-spring artifact 잠시 살펴볼 필요가 있다. jersey-spring artifact 자체적으로 spring 프레임웍을 포함하고 있기 때문에, 우리가 정의한  Spring framework 중복 가능성이 있다. 그래서, jersey-spring dependency 의해서 spring framework 중복적으로 가지고 오지 않도록 해당 모듈들을 다음과 같이 exclude(제외)하도록 선언한다.

<!--  jersey + spring depdendency  -->

     <dependency>

                       <groupId>com.sun.jersey.contribs</groupId>

                       <artifactId>jersey-spring</artifactId>

                       <version>1.17</version>

                       <exclusions>

                              <exclusion>

                                      <groupId>org.springframework</groupId>

                                      <artifactId>spring</artifactId>

                              </exclusion>

                               <exclusion>

                                      <groupId>org.springframework</groupId>

                                      <artifactId>spring-core</artifactId>

                              </exclusion>

                              <exclusion>

                                      <groupId>org.springframework</groupId>

                                      <artifactId>spring-web</artifactId>

                              </exclusion>

                              <exclusion>

                                      <groupId>org.springframework</groupId>

                                      <artifactId>spring-beans</artifactId>

                              </exclusion>

                              <exclusion>

                                      <groupId>org.springframework</groupId>

                                      <artifactId>spring-context</artifactId>

                              </exclusion>

                       </exclusions>

               </dependency>

자아. 이제 빌드 스크립트는 Jersey + Mybatis + Spring DI 이용한 개발 준비가 되었다. 그러면 실제 코딩에 들어가 보자

java/terry/restapi 아래, MVC 모델에 맞춰서 service,dao,model 디렉토리를 만들고 아래와 같이 클래스들을 구현한다.

 

java/terry/restapi/dao/ContactDao.java

package terry.restapi.dao;

 

import java.util.HashMap;

 

import terry.restapi.model.ContactVo;

 

public interface ContactDao {

       

        public void create(ContactVo contact);

        public ContactVo get(String email);

        public void delete(String email);

        public void update(String email,ContactVo contact);

}

 

java/terry/restapi/dao/ContactImpl.java Mybatis 이용한 contact 테이블에 대한 CRUD 구현한다.

package terry.restapi.dao;

 

import org.mybatis.spring.support.SqlSessionDaoSupport;

 

import terry.restapi.model.ContactVo;

 

public class ContactDaoImpl extends SqlSessionDaoSupport implements ContactDao {

 

        public void create(ContactVo contact) {

               // TODO Auto-generated method stub

 

        }

 

        public ContactVo get(String email) {

               // TODO Auto-generated method stub

               ContactVo contact = (ContactVo)getSqlSession().selectOne("contactdao.getUserByEmail", email);

               return contact;

        }

 

        public void delete(String email) {

               // TODO Auto-generated method stub

 

        }

 

        public void update(String email, ContactVo contact) {

               // TODO Auto-generated method stub

 

        }

 

}

 

java/terry/restapi/model/ContactVo.java 앞에서 이미 구현했기 때문에, 앞의 코드 참고

 

다음은 Jersey 이용한 REST API 구현이다. 아래 코드는 편의상 create,select,update 구현하였다.

package terry.restapi.service;

 

import javax.ws.rs.Consumes;

import javax.ws.rs.GET;

import javax.ws.rs.POST;

import javax.ws.rs.PUT;

import javax.ws.rs.Path;

import javax.ws.rs.PathParam;

import javax.ws.rs.Produces;

import javax.ws.rs.core.MediaType;

import javax.ws.rs.core.Response;

 

import terry.restapi.dao.ContactDao;

import terry.restapi.model.ContactVo;

 

@Path("/contact")

public class ContactService {

        static ContactDao dao = null;

        public void setContactDao(ContactDao dao){this.dao = dao;}

        public ContactService(){

               //if(dao == null)      setContactDao(new ContactDao());

        }

       

        /**

         * Create Contact Record

         * @param contact

         * @return

         */

        @POST

        @Consumes(MediaType.APPLICATION_JSON)

        public Response create(ContactVo contact){

               dao.create(contact);

               return Response.status(200).entity(contact).build();

 

        }

        /**

         * Query Contact record by email id

         * @param email

         * @return

         */

        @GET

        @Produces(MediaType.APPLICATION_JSON)

        @Path("{email}")

        public ContactVo get(@PathParam("email") String email){

               return dao.get(email);

        }

        /**

         * Upadte Contact Record by email

         * @param email

         * @param contact

         * @return

         */

        @PUT

        @Path("{email}")

        @Consumes(MediaType.APPLICATION_JSON)

        @Produces(MediaType.APPLICATION_JSON)

        public Response  update(@PathParam("email") String email, ContactVo contact){

               dao.update(email, contact);

               return Response.status(200).entity(contact).build();

        }

}

 

 

자아 코드 구현이 끝났다.

이제 코드를 실행하기 위해서는 다음과 같은 추가 작업이 필요하다.

/webapp/WEB-INF/config/spring-context.xml Spring Bean 정의하기 위한 spring context 파일 작성

<?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:context="http://www.springframework.org/schema/context"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

 

        <!--  load configuration file -->

         <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

          <property name="locations">

           <value>/WEB-INF/config/config.properties</value>

          </property>

         </bean>

         

        <!--  create rest service object and inject dao -->

        <bean class="terry.restapi.service.ContactService" id="contactService">

               <property name="contactDao" ref="contactdao" />

        </bean>

        <!-- declare dao object  -->

        <bean class="terry.restapi.dao.ContactDaoImpl" id="contactdao">

               <property name="sqlSessionFactory" ref="sqlSessionFactory" />

        </bean>

       

        <!--

               mybatis configuration

               sqlSessionFactory & sqlSessionTemplate are required

         -->

        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

               <property name="dataSource" ref="dataSource" />

               <property name="configLocation" value="/WEB-INF/config/mybatis-config.xml"/>

        </bean>

        <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">

               <constructor-arg ref="sqlSessionFactory" />

        </bean>

       

        <!-- 

               data source configuration

               for testing purpose , it uses simple jdbc datasource

         -->

        <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">

               <property name="driverClass" value="${mybatis.jdbc.driverclass}"/>

               <property name="url" value="${mybatis.jdbc.url}" />

               <property name="username" value="${mybatis.jdbc.username}" />

               <property name="password" value="${mybatis.jdbc.password}" />

        </bean>

</beans>

 

그리고 위의 파일 내용을 보면, /WEB-INF/config/config.properties라는 파일을 읽게 되어 있는데, 여기에는 MYSQL DBMS 접속에 필요한 URL,PORT,사용자 id,passwd등이 들어간다.

/webapp/WEB-INF/config/config.properties

 

mybatis.jdbc.driverclass=com.mysql.jdbc.Driver

mybatis.jdbc.url=jdbc:mysql://localhost:3306/development

mybatis.jdbc.username=developer

mybatis.jdbc.password=developer

 

/webapp/WEB-INF/config/mybatis-config.xml MyBatis 사용하는 데이타 베이스 연결 정보등의 설정 파일

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "HTTP://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

 <settings>

  <setting name="cacheEnabled" value="false" />

  <setting name="useGeneratedKeys" value="true" />

  <setting name="defaultExecutorType" value="REUSE" />

 </settings>

 

 <mappers>

  <mapper resource="sqlmap/ContactDao_map.xml" />

 </mappers>

</configuration>

 

/webapp /WEB-INF/web.xml 초기에 Spring Context Jersey framework 로딩하기 위한 설정

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

 

        <!--  load spring context configuration -->

        <context-param>

               <param-name>contextConfigLocation</param-name>

               <param-value>

                       /WEB-INF/config/spring-context.xml

                       <!-- /WEB-INF/config/mybatis-context.xml -->

               </param-value>

        </context-param>

 

        <!--  load listener  -->

        <listener>

               <listener-class>

                       org.springframework.web.context.ContextLoaderListener

               </listener-class>

        </listener>

       

        <!--  configure jersey/JSON servlet -->

        <servlet>

          <servlet-name>Jersey Web Application</servlet-name>

               <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>

                     <init-param>

                              <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>

                              <param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>

                 </init-param>

                 <init-param>

                  <param-name>com.sun.jersey.config.property.packages</param-name>

                  <param-value>terry.restapi</param-value>

                </init-param>

                <init-param>

                              <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>

                              <param-value>true</param-value>

                       </init-param>

                          <load-on-startup>1</load-on-startup>

            </servlet>

       

            <servlet-mapping>

               <servlet-name>Jersey Web Application</servlet-name>

                <url-pattern>/*</url-pattern>

            </servlet-mapping>

</web-app>

 

/resources/sqlmap/ContactDao_map.xml MyBatis 실행하는 SQL 들어 있는 SQL Mapper 파일 작성

<?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="contactdao">

        <!-- 

               select user by email id

        -->

        <resultMap type="terry.restapi.model.ContactVo" id="resultmap.terry.restapi.model.ContactVo">

               <id column="email" property="email"/>

               <result column="name" property="name"/>

               <result column="phone" property="phone"/>

        </resultMap>

        <select id="getUserByEmail" parameterType="String" resultMap="resultmap.terry.restapi.model.ContactVo">

               select email,name,phone

               from contact_table

               where email = #{email}

        </select>

</mapper>

 

여기 까지 구현하였으면 디렉토리는 다음과 같은 형태가 된다..


 

이제 mvn package 명령어를 이용하여 war 파일을 만들고, tomcat 배포한후,

MySQL DB 아래와 같은 scheme contact_table 만들면 실행을 위한 준비가 끝난다.

 


수동으로 Tomcat 설치하고 실행하고 배포하면 까다롭기도 하거니와, 개발자 마다 다른 버전이나 다른 설정을 있기 때문에,  이번에는 mvn 스크립트내에서 빌드 과정내에, Tomcat 자동으로 기동 시키고 배포하는 스크립트를 추가해보자.

 

과정은 maven 플러그인이라는 기능을 이용하면 된다.

pom.xml <project> 엘리먼트 아래 다음과 같은 내용을 추가한다.

<build>

    <finalName>restapi</finalName>

         <plugins>

              <!-- 

                tomcat7 plugin

                caution!!. mvn tomcat:xxx will invoke default plugin

                do. mvn tomcat7:xxx

                -->

              <plugin>

                          <groupId>org.apache.tomcat.maven</groupId>

                  <artifactId>tomcat7-maven-plugin</artifactId>

                  <version>2.1</version>

                          <configuration>

                                 <warSourceDirectory>${basedir}/target/${artifactId}</warSourceDirectory>

                          </configuration>

             </plugin>

         </plugins>

  </build>

위의 내용은 tomcat 7 플러그인을 추가하여, 빌드 후에 tomcat 기동할 있게 해주며, tomcat 실행시 ${basedir} (pom.xml 있는 프로젝트의 루트 디렉토리)아래 /target/terry 디렉토리를 war root 디렉토리로 인식하고 기동하게 한다.

${artifactid} maven 프로젝트 생성시 지정했던 프로젝트의 이름으로, war 빌드 스크립트는 별도의 지정이 없는한, war파일명을 ${artifactid}.war ${basedir}/target/ 아래 생성한다. 또한 컴파일 과정에서 war파일이 풀린 모든 파일들을 위치해놓는다.

 

자아 이제 모든 스크립트가 완성이 되었다.

%mvn package tomcat7:run

실행해보자

package 컴파일 war파일과 war 파일이 풀린 모든 파일들(exploded war) 만들게 하고

tomcat7:run tomcat7 위에서 만든 exploded war파일들을 읽어서 기동하게 한다.

 

이제 로컬 환경에서 배포와 실행이 되었으면 REST API 호출하여 테스트를 해보자. 간단한 테스트를 위해서 Google Chrome Browser Advanced REST Client 사용하였다.

 

 



 

 

Jersey로 구현하는 자바 REST 서비스


 

이번 회에서는 REST의 개념을 바탕으로 JAVA 언어로 REST를 구축하는 방법에 대해서 알아보도록 한다.

JAVA 기반의 REST구현 방법에는 여러 가지가 있으나 근래에 웹서비스처럼 REST도 구현을 쉽게 도와줄 수 있는 프레임웍을 제공한다. 웹서비스의 구현 개발 표준이 JAX-WS였다면 REST에 대한 구현 표준은 JAX-RS이며 그 레퍼런스 구현으로는 Apache CXF Sun(지금은 오라클) Jersey가 있다. 본 문서는 Sun Jersey를 기준으로 작성되었다.

기본 REST 서비스 구현

먼저 이클립스를 인스톨하고 New > Project > Dynamic Web Project로 새로운 프로젝트를 생성한다.



프로젝트가 생성되었으면 Jersey를 사용하기 위해서 라이브러리의 설정등을 해야한다.



REST를 실행하는 서블릿 컨테이너를 서블릿으로 등록하고, 이 웹애플리케이션의 모든 URL REST 컨테이너내에서 실행되도록 <servlet-mapping> 부분에 url-pattern으로 정의한다. (특정 URL 패턴만 REST로 실행하도록 하고 싶으면 이 url-pattern 부분만 조정하면 된다.)

이제 앞의 연재에서 .NET WCF로 진행한 것과 같은 예제를 JAVA Jersey로 구현해보자



먼저 ValueObject를 위와 같이 구현한다. ValueObject REST 구현체상에서 JAVA XML을 상호 변환하여 맵핑 시켜주는 역할을 하며 이는 Java XmlBinding 프레임웍인 JAXB를 사용한다. Serialize되기 때문에 클래스 선언시에는 반드시 Serializable 인터페이스를 implements 하도록 한다.

반드시 클래스 앞에 @XmlRootElement Annotation을 이용해서 본 클래스가 JAXB를 이용해서 XML 변환을 사용할것이라는 것을 지정한다. 이때 name을 지정해서 XmlElement의 이름을 명시적으로 지정한다. (본 예제에서는 클래스명이 ContactVo이기 때문에 명시적으로 이름을 정해주지 않으면 Xml 로 변환시 클래스 이름을 따라서 <contactVo>로 변환된다.)

위의 ValueObject Xml로 변환되면 다음과 같은 모양이 된다.

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

- <Contact>

  <email>111</email>

  <name>22</name>

  <phone>123123</phone>

  </Contact>

다음으로는 이 ValueObject들을 저장할 Dao를 작성한다. 나중에 DB에 연동하고 싶으면 이 Dao 부분을 DB 연동 부분으로 바꾸면 되지만, 본 예제는 REST 자체의 구현에 집중하기 때문에, 간단하게 HashTable ContactVo email Key 값으로 저장한다



다음으로는 실제 CREATE,READ,UPDATE,DELETE를 지원하는 REST 서비스를 작성한다.



클래스에 @Path 어노테이션을 이용하여 REST 리소스의 URI 경로를 정의한다. URI FULL 경로는 http://웹서버/{WebApplication(WAR) URI}/{@Path 어노테이션으로 정의한 경로}이다.
REST 애플리케이션은 FirstREST라는 경로에 배포되는 것으로 설정하였기 때문에 이 리소스의 경로는 http://localhost/FirstREST/Contact이 된다.

각각의 HTTP Method Java 메서드를 맵핑 시키기 위해서는 각각 @PUT,@GET,@POST,@DELETE  Annotation을 사용한다. 여기서 주목해야 하는 부분은 URI를 통해서 인자를 넘겨 받는 부분인데, @Path Annotation을 사용하여 URI상에 인자가 있을 경우 정의하고, 인자를 @PathParam을 통해서 실제 Java 변수와 맵핑 시켜줘야 한다. 예를 들어 ../Contact/{email} URI에서 {email}을 인자로 사용하고 싶으면 위의 Delete 메서드와 같이 @Path(“{email}”)로 정의하고, URI @PathParam(“email”)String email 로 맵핑 시켜줘야, Java 변수 email로 값이 맵핑 된다.

여기까지 했으면 기본적인 REST가 컴포넌트가 완성되었다. 이제 컴파일을 하고 Eclipse에서 Project > Export 메뉴를 이용하여 WAR Export하여 Tomcat이나 JBoss등의 Web Application 서버에 배포하고 테스트를 진행하면 된다.

REST 구현에 있어서 자주 사용되는 Annotation들은 다음과 같다.

@QueryParam

URI QueryString 을 사용하여 method parameter를 전달한다. Parameter의 값은 자동으로 decoding 되며 자동 deocoding을 사용하지 않을때에는 @Encoded 를 사용한다.

@PathParam

URI path String 을 사용하여 method parameter를 전달 한다. Parameter의 값은 자동으로 decoding 되며 자동 deocoding을 사용하지 않을때에는 @Encoded 를 사용한다.

@Context

annotation 을 사용하여 UriInfo , Request, HttpHeader 같은 값들을 class field , method parameter로 전달한다.

@CookieParam

HTTP Cookie 의 값을 method parameter로 전달한다.

@HeaderParam

HTTP header 의 값을 method parameter  전달한다.

REST 컴포넌트 테스트 (SOAP UI)

불행히도 Sun Jersey Implementation .NET WCF와는 달리 별도의 REST 테스트 기능을 지원하지 않는다. 그래서 별도로 테스트 환경을 설정해야 한다. 가장 널리 사용되는 REST 테스트 도구로는 soapUI라는 도구가 있다. http://www.soapui.org 에서 다운받을 수 있다. 다운을 받아서 설치를 한 후 실행을 한다.

File > New SoapUIProject를 선택하여 새로운 테스트 프로젝트를 생성한다. 생성된 프로젝트에서 New REST Service를 선택하면 새로운 서비스 EndPoint를 넣게 되는데, 서비스명은 테스트할 서비스 이름 (아무거나)을 넣고, EndPoint에는 웹서버의 URL(IP,PORT)를 넣는다.



다음으로 Resource를 정의해야 하는데, Create,Delete,Put ./Contact/{email} 형태의 리소스이고, Post ./Contact 형태의 리소스이기 때문에, 먼저 ./Contact 리소스를 만들고 ./Contact/{email}리소스를 그 차일드 리소스로 만든다. 위에서 만든 서비스에서 New Resource를 선택하고 리소스명은 “Contacts” EndPoint /FirstREST/Contact (FirstREST는 이 REST 서비스가 배포되는 URI) 을 입력한다.



다음으로 해당 리소스에 New REST Method를 이용하여 새로운 메서드를 추가하는데, POST에 대한 메서드를 추가한다.



POST REST Request가 만들어 졌다. 이제 해당 Request를 선택하여 테스트창을 열어보자



테스트 창에서 아래 부분에 POST로 보낼 HTTP BODY XML 데이터를 입력하고 실행 버튼을 누르면 POST 요청을 보낸 것이다좌측의 Raw 탭을 이용하면 실제로 전송되는 HTTP Request를 볼 수 있다.



이제 HTTP GET을 이용해서 Resource가 제대로 생성되었는지 확인을 하자.

앞에서 생성한 Contacts 리소스에서 오른쪽 버튼을 클릭하여 New Child Resource 메뉴를 선택한다. Resource 이름을 Contact으로 정의하고, end Point“/{email}”을 추가하고, Extract Param 버튼을 클릭하면 이 {email}을 변수로 취급해준다. 이때 중요한 것이 Style인데, Template으로 선택되어야 한다. Style에 따라서 변수를 HTTP URI에 실어서 보낼지, HTTP Header에 넣을지, Query String등에 넣을지를 선택할 수 있다.



마찬가지로 New Method 메뉴가 나오는데, HTTP GET 메서드로 선택하고 getMethod라는 이름으로 새로운 메서드를 정의한다.



새로 생성한 메서드를 테스트 해보자, 위의 그림처럼 좌측 부분에 앞에서 정의한 email 변수에 값을 지정할 수 있다. 앞의 POST 메서드에서 “carry” 라는 이름으로 리소스를 만들었으니 emailcarry를 지정하고 실행 버튼을 누르면 위의 좌측 메뉴 처럼 XML로 리턴값을 보여준다.

같은 방법으로 PUT,DELETE에 대한 테스트 케이스를 만들 수 있다

본 예제에서는 가장 기본적인 부분만 설정하였는데, SoapUI의 경우 부하테스트를 통한 성능 측정까지 가능하기 때문에 Micro benchmark등에 활용이 가능하니 참고하기 바란다.

List 형 데이터 처리

자아 지금까지 단순한 REST 구현에 대해서 알아보았다. 이번에는 List 타입을 어떻게 핸들링하는지 알아본다. List 타입을 리턴하기 위해서는 ContactService.java에 다음 메서드를 추가한다.

이때 반드시 리턴 데이터 타입에 <ContactVo> List 개별 항목이 어떤 데이터 타입을 가지고 있는지를 정의해줘야 자바 객체를 JAXB XML 변환할 있다.

//ContactService.java 추가

@GET

    @Produces("text/xml")

    public List<ContactVo> GetList()

    {

        return dao.getList();      

    }

 

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

- <contactVoes>

- <Contact>

  <email>bycho</email>

  <name>Byungwook.Cho</name>

  <phone>123123</phone>

  </Contact>

- <Contact>

  <email>carry</email>

  <name>Carry.Cho</name>

  <phone>123123</phone>

  </Contact>

</contactVoes>

에러처리

에러 처리역시 매우 단순하다.

WebApplicationException을 사용하되, HTTP Error코드를 인자로 넘겨주면 된다.

Throw new WebApplicationException (400)                             

@Consumes @Produce

JAX-RS에서는 @Consume @Produce라는 Annotation을 지원하는데, @Consume HTTP Request를 받을 때 어떤 형태로 받을 것인 가이다. 현재는 디폴트로 xml request로 받게 되어 있지만, json이나 mime 타입 같은 확장 타입도 지원한다반대로 @Produce는 생성해내는 결과값의 형태에 대해서 정의하는데, @Produce에서 단순하게 타입을 json으로만 정해주면 결과값이 XML이 아닌 json 형태로 리턴된다..

 

3회에 걸쳐서 REST에 대한 개념, .NET JAVA를 이용한 실제 코드 구현에 대해서 살펴보았다. 다음회에서는 마지막으로 엔터프라이즈 시스템에 REST를 적용할 때 필요한 아키텍쳐 적인 면에 대해서 살펴보도록 하겠다.

REST의 반격?

아키텍쳐 /WEB 2.0 | 2008.12.08 14:51 | Posted by 조대협
SPEC도 없고, 적당한 구현 프레임웍도 없던 REST가 드디어 반격을 시작하는지?
여기저기서 들려오는 소리가 대부분 REST에 대한 소식이다.
WSDL처럼 REST의 스펙을 정의하는 WADL
REST 프로그래밍 스펙이 JSR 311-JAX RS로 등록이 되고
Sun에서는 JAX RS의 Implementation체인 Jersey (https://jersey.dev.java.net/) 도 있고..
WSDL2.0에서도 REST스펙이 추가 되었다하니
아마 REST 기반의 개발이 가속화 되지 않을까?
금년에는 유난히 REST에 대한 이야기가 많네 그랴..

'아키텍쳐  > WEB 2.0' 카테고리의 다른 글

REST 아키텍쳐에 대한 연재를 시작합니다.  (2) 2009.05.27
REST 연재-1회 REST 아키텍쳐의 기본  (15) 2009.05.27
AJAX Overview  (1) 2009.04.09
REST의 반격?  (0) 2008.12.08
오픈 환경과 우물안의 개구리....  (0) 2008.09.16
Open Social Zembly..  (0) 2008.09.16