에러내용:

 

 Servlet.service() for servlet article threw exception
org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [null]; error code [0];   

 

이런 에러문구는 resultType 을 찾지 못하는 경우 발생한다.

 

예를 들면 ibatis sqlMap 설정에서 result 클래스명이 잘못 지정된 경우이거나

ibatis에서 정의한 resultMap 과 java 클래스간 내용이 일치하지 않을 때 발생한다.

 

일반적인 경우 vo작성시 getter/setter generater를 사용하여 만드는 경우에는 별일이 없지만

직접 작성 또는 수정후 재생성시 getter 또는 setter 메소드가 잘못 생성되었거나 중복되는 경우 위와 같은 에러가 발생한다.

 

특히 내부변수값을 변경하고자 하는 경우 자동생성시는 관련 setter/getter 메소드를 삭제후 구동하여야....ㅋ..

 

 

 
 

오픈소스를 이용한 시스템 통합 그 네번째 이야기입니다..*^^*

 

1회, 오픈소스를 이용한 시스템 통합, 그 즐거운 도전의 시작
2회, 첫번째 도전! 스트럿츠와 벨로시티의 통합

3회, 약한 결합도 아키텍처를 위한 대안 기술, 스프링
4회, OR 맵핑 도구의 선두 주자, 하이버네이트

 

지난 글에서는 스프링 프레임워크를 소개하고 스프링의 WebMVC 모듈을 이용해 웹 애플리케이션을 개발하는 방법과 스트럿츠와의 연동 방법에 대해 살펴보았다. 이번 글에서는 OR 맵핑 도구의 선두 주자인 하이버네이트에 대해 살펴보고, 하이버네이트가 어떻게 스프링과 결합하여 시너지 효과를 발휘하는지 살펴보도록 하자.

엔터프라이즈 애플리케이션 개발에서는 데이터베이스와 연동하여 CRUD 연산을 수행할 퍼시스턴스 계층을 만들고, 유지 보수하는 작업에 상당한 시간과 노력을 투자하게 된다. 만일 데이터베이스의 스키마가 바뀌는 경우에는 애플리케이션의 나머지 부분도 크게 변경해야 한다.

대부분의 애플리케이션 개발이 객체지향 기술을 사용하고 있는 데 반해, 데이터베이스는 관계형 데이터 모델을 사용하는 RDBMS를 이용하고 있다. 이로 인해 객체 모델링과 관계형 데이터 모델링 사이에 개념적 불일치가 존재하게 되고, 거의 모든 프로젝트에서는 퍼시스턴스 계층의 구현에 심각한 수준의 복잡도 부담을 떠안게 되는 것이다.

이러한 문제들을 해결하기 위해 OR 맵핑을 자동화해 주는 다양한 도구와 기술들이 등장했다. EJB의 CMP(컨테이너 관리 퍼시스턴스) 방식의 엔티티 빈도 있고, CocoBase나 TopLink와 같은 전문 제품들도 있다. 최근 OR 맵핑 도구로써 주목을 끌고 있는 것은 XML을 사용하는 퍼시스턴스 프레임워크이다.

퍼시스턴스 프레임워크의 대표 주자는 JDO(Java Data Object)의 아파치 구현인 OJB(ObJect Relational Bridge)와 iBatis 그리고 하이버네이트(Hibernate)이다. OJB의 경우 썬의 표준 스펙인 JDO를 구현한 점과 오픈소스의 산실인 아파치 프로젝트로 진행된다는 점이 강점이다. OJB에 관해서는 이미 연재된 적이 있으므로 자세한 내용은 해당 글을 찾아보기 바란다.

최근에는 OJB보다 iBatis와 하이버네이트에 대한 관심이 더 높은 듯 하다. iBatis와 하이버네이트의 가장 큰 차이점은 맵핑 방법에 있다. 둘 다 OR 맵핑을 위해 XML을 사용하고 있지만, iBatis는 SQL 쿼리를 사용해서 맵핑하는 반면에 하이버네이트는 테이블의 컬럼과 자바 객체의 속성을 맵핑한다.

iBatis는 개발자들에게 익숙한 SQL 쿼리를 직접 사용하기 때문에 개념적인 오버헤드가 적은 편이다. 따라서 OR 맵핑 도구에 익숙하지 않은 개발자라고 하더라도 쉽게 적응할 수 있다. 또한 DB 전문가들의 튜닝 노하우가 접목된 SQL을 자유롭게 이용해 각 DBMS의 장점을 적극 수용할 수 있다는 것도 장점이다. 하지만 기존의 개발 방법과 유사한 점이 많기 때문에 개발 속도나 문제를 해결하는 패러다임 자체를 눈에 띌 만큼 개선해 주지는 못한다.

하이버네이트의 경우는 iBatis와 반대이다. 하이버네이트는 SQL 자체를 내부적으로 숨겨서 개발자에게 보여주지 않는다. 데이터의 기본 CRUD 연산은 맵핑된 자바 객체를 통해 이루어진다. 하이버네이트를 보다 더 객체지향적이며, 자바다운 방법으로 문제를 해결한다. 하지만 OR 맵핑의 개념에 익숙해지기까지 오랜 시간이 걸리는 단점이 있어, 실무에서 사용하기 위해서는 기술 교육에 상당한 시간이 소요되는 단점이 있다.

결론적으로 엔티티 빈의 대안으로 거론되는 퍼시스턴스 프레임워크 중 당장 실무에서 사용하기에 적합한 것은 iBatis이다. 하지만 팀원들의 교육에 충분한 시간을 할애할 여유가 있다면 하이버네이트나 OJB의 사용을 권하고 싶다.

하이버네이트의 개요
하이버네이트는 현재 세계적으로 가장 많은 관심을 받고 있는 자바 기반의 OR 맵핑 도구이다. 하이버네이트는 LGPL로 배포되고 있다. 따라서 오픈소스 프로젝트뿐 아니라 상업용 프로젝트에서도 자유롭게 사용할 수 있다.

하이버네이트는 오라클, DB2, MySQL을 비롯한 대부분의 데이터베이스를 지원하며, 스트럿츠, 스프링, 웹워크 등의 유명 프레임워크들과 웹로직, 웹스피어, JBoss, 톰켓 등의 각종 애플리케이션 서버들과도 쉽게 연동될 수 있다.

하이버네이트는 복합 타입은 물론, 컬렉션과 객체 관계를 지원한다. 객체에 대한 퍼시스턴스를 제공하는 것은 물론이고, 효율적인 캐싱 계층과 JMX(Java Manage eXtensions)를 지원한다. 또한 데이터베이스로부터 객체를 얻어올 수 있는 퀴리 언어인 HQL(Hibernate Query Language)도 제공된다.  

하이버네이트의 가장 큰 장점은 문서화가 잘 되어 있고, 많은 자료를 확보한 커뮤니티의 지원이 충실하다는 점이다. 또한 수많은 노가다성 코딩을 하지 않도록 자동화된 프레임워크를 제공하고 있음에도 불구하고, 일반적인 JDBC 구현에 비교해도 뒤지지 않는 높은 성능을 보장한다.

하이버네이트가 지원하는 높은 성능은 Lazy Initializing Proxy인 CGLIB와 각종 캐시 지원으로 이뤄지는데, 하이버네이트의 창시자인 가빈 캉(Gavin Kang)은 한때 동일한 SQL을 호출하는 JDBC 프로그램에 비해 하이버네이트가 느리다는 것을 증명하는 사람에게는 자비로 100달러를 지불하는 이벤트를 벌이기도 했다. 물론 그 돈을 받아갔다는 사람은 없다.

현재 하이버네이트의 최신 버전은 2.1.6이며, 3.0 알파 테스트 버전이 배포되고 있다. 하이버네이트는 <그림 1>에서 보여지는 것처럼 애플리케이션과 데이터베이스 사이의 퍼시스턴스 계층을 담당한다.

<그림 1> 하이버네이트의 기본 구조


하이버네이트는 퍼시스턴스 서비스를 애플리케이션에 제공하기 위해 몇 가지 설정 파일을 필요로 한다. 기본 설정 파일인 hibernate.properties는 데이터베이스와 접속하거나 스키마를 생성하는 데 필요한 기본 정보를 담고 있다. XML 맵핑 파일은 CLASS_NAME.hbm.xml의 형식을 가지며 영속 객체와 데이터베이스 테이블을 연결하는, 즉 OR 맵핑에 필요한 각종 메타 데이터를 제공한다.

예제로 알아보는 하이버네이트의 기초
이제 간단한 예제를 통해 하이버네이트의 기본 개념을 익혀보도록 하자. 살펴볼 예제는 하이버네이트 홈페이지를 통해 제공되는 블로그 예제이다. 이 예제에서 사용되는 테이블은 BLOGS와 BLOG_ITEMS이다. 1:M 관계를 구현한 예제이며, 1:1 또는 N:M 예제 또한 제공되므로 참고하기 바란다. 이 예제는 JDK 1.4.2 환경에서 테스트되었으며, 사용한 하이버네이트 버전은 2.1.6이다. IDE는 이클립스 3.1에서 Hiber8ide 플러그인을 설치해서 사용했고, 데이터베이스는 HSQLDB 1.7.2를 이용했다.

<그림 2> 블로그 예제의 OR 맵핑 관계


예제는 총 4단계로 구성되며, 하이버네이트 맵핑 파일의 작성에서부터, 퍼시스턴스를 표현하는 POJO와 DB 스키마가 생성되는 과정을 보여주게 될 것이다. 그리고 마지막으로 하이버네이트를 이용해서 DB에 저장된 값을 가져오는 방법을 살펴볼 것이다.

<그림 3> 블로그 예제 진행 과정


1단계. 하이버네이트 맵핑 파일 작성
우선 하이버네이트 맵핑(HiBernate Mapping) 정보를 담은 XML 파일인 hbm.xml을 테이블마다 생성한다. 각각의 hbm.xml 파일은 hibernate-mapping이라는 루트 엘리먼트를 가지며, 패키지 정보를 package 속성으로 주고 있다.


Blog.hbm.xml
<hibernate-mapping package="vssh.model">
  <class name="Blog" table="BLOGS" lazy="true">
<cache usage="read-write"/> <!--성능 관련 element -->  
<id name="id" type="long" column="BLOG_ID">
        <generator class="native"/>
      </id>
      <property name="name" type="string" column="NAME" not-null="true" unique="true"/>
      <list name="items" inverse="true" lazy="true" order-by="DATE_TIME" cascade="all">
        <key column="BLOG_ID"/>
        <one-to-many class="vssh.model.BlogItem"/>
      </list>
  </class>
</hibernate-mapping>

BlogItem.hbm.xml
<hibernate-mapping package="vssh.model">
  <class name="BlogItem" table="BLOG_ITEMS" dynamic-update="true">
<cache usage="read-write"/> <!--성능관련 element -->
    <id name="id" type="long" column="BLOG_ITEM_ID">
      <generator class="native"/>
    </id>
    <property name="title" type="string" column="TITLE" not-null="true"/>
    <property name="text" type="string" column="TEXT"  not-null="true"/>
    <property name="datetime" type="date" column="DATE_TIME" not-null="true"/>
    <many-to-one name="blog" class="vssh.model.Blog" column="BLOG_ID" not-null="true"/>
  </class>
</hibernate-mapping>


각각의 hibernate-mapping 엘리먼트는 class 엘리먼트를 포함한다. class 엘리먼트는 OR 맵핑을 위한 핵심 요소로 각 속성은 <표 1>과 같은 의미를 가진다.

<표 1> class 엘리먼트의 속성


class 엘리먼트 내에 포함된 각각의 엘리먼트에 대해 살펴보자. id는 퍼시스턴트 객체들을 구분하기 위해 사용되는 프라이머리 키와 같은 특수 속성이다. generator는 퍼시스턴스 클래스의 코드 생성 방법을 결정짓는 속성이다. increment, identity, sequence, hilo, seqhilo, uuid.hex, uuid.string, native, assigned, foreign의 10가지 알고리즘이 제공되는데 예제에서는 DB에 종속적인 native 방식을 채택했다.

property는 ID가 아닌 일반 속성을 정의한다. 뒤에서 생성시킬 POJO에는 각 속성별로 한 쌍의 getter/setter가 자동으로 생성된다. 속성에는 컬럼의 길이, 타입 정보, 널(null) 값의 사용 가능 유무 등을 기술할 수 있다. id와 property에서 설정하는 타입은 하이버네이트의 타입 지원을 참고하기 바란다. 앞에서 기술한 타입들은 모두 하이버네이트에서 기본적으로 제공하는 기본 타입이다.

각 퍼시스턴스 객체들간의 관계 설정에는 one-to-many, many-to-one과 같은 엘러먼트가 사용된다. 다음의 코드는 설정 파일에서 관계 설정 부분만을 따로 떼어낸 것이다.


Blog.hbm.xml
<list name="items" inverse="false" lazy="true" order-by="DATE_TIME" cascade="all">
      <key column="BLOG_ID"/>
      <one-to-many class="vssh.model.BlogItem"/>
</list>
BlogItem.hbm.xml
<many-to-one name="blog"  class="vssh.model.Blog"
column="BLOG_ID" not-null="true"/>


mapping 파일로 생성된 Blog 클래스에는 다음과 같은 코드가 포함된다.


private Set items;


list 엘리먼트의 위치에는 <set>, <list>, <map>, <bag>, <array>와 <primitive-array>가 오는 것이 가능하다. 외래 키(Foreign Key)가 정의되며 키를 중심으로 1:1, 1:M , N:M 등의 연결이 컬렉션 API를 통해 표현되는 것이다. 위의 예를 보면 즉 items라는 이름을 가진 Set 컬렉션이 BlogItem의 집합을 담고 있으며, 각 BlogItem은 BLOG_ID 컬럼을 외래 키로 사용해서 자신이 소속된 Blog를 참조하게 되는 것을 알 수 있다.

list 엘리먼트에는 lazy란 속성이 있는데 기본 값은 false이다. lazy 속성을 true로 구성하게 되면 Blog를 구성할 때 BlogItem의 정보를 컬렉션에 담지 않는다. 즉, 애플리케이션에서 BlogItem의 정보가 필요할 때까지 데이터베이스에서 값을 가져와서 컬렉션에 담는 행위를 늦춤으로써 결과적으로 성능을 향상시키는 효과를 얻게 된다. inverse 속성은 두 객체(Blog, BlogItem) 간의 관계에서 양방향성을 나타낸다.

2단계. Ant를 이용한 POJO 의 생성
하이버네이트 맵핑 파일의 작성이 끝나면 하이버네이트의 Hbm2Java 기능을 이용해서 POJO 객체를 자동 생성할 수 있다. 다음의 Ant 스크립트에서 hbm2java로 정의된 태스크를 이용해서 설정된 codegen이라는 타겟을 확인할 수 있다.


build.xml
<taskdef name="hbm2java" classname="net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"
    classpathref="project.class.path"/>
<!-- hbm to java code codegenarator !!! -->
<target name="codegen">
    <hbm2java output="${src.dir}">
        <fileset dir="${src.dir}">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </hbm2java>
</target>


생성된 POJO의 클래스 다이어그램은 <그림 4>에서 확인할 수 있다.


<그림 4> Hbm2JavaTask[Hibernate extentions] class를 이용해 생성된 POJO 파일


3단계. Ant를 이용한 DB 스키마 생성
이제 SchemaExportTask 기능을 이용해서 DB 스키마를 자동 생성해 보도록 하자. 다음의 Ant 스크립트에서 schemaexport란 이름의 태스크를 정의해서 schame란 이름의 타겟에서 이용하고 있음을 확인할 수 있다. 예제에서는 hbm.xml 파일을 직접 사용하지 않고, fileset을 주석으로 막아 hibernate.cfg.xml를 설정 파일로 사용했다. 생성된 스키마 생성 스크립트는 schema-export.sql 파일에서 확인 가능하다.


build.xml
<target name="schema">
    <taskdef name="schemaexport"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
      classpathref="project.class.path"/>
    <schemaexport drop="no" text="no" output="schema-export.sql"
        config="${build.dir}/hibernate.cfg.xml">
        <!--
        <fileset dir="${build.dir}">
               <include name="**/*.hbm.xml"/>
           </fileset>-->
    </schemaexport>
</target>


설정 파일로 사용된 hibernate.cfg.xml 파일은 다음과 같다. HSQLDB 연결을 위한 기본 설정과 성능 관련 엘리먼트, 그리고 맵핑 파일 정보를 담고 있다.


hibernate.cfg.xml
...DTD 생략 ...
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.connection.driver_class">
org.hsqldb.jdbcDriver
</property>
    <property name="hibernate.connection.url">
jdbc:hsqldb:data/hibernateblog
</property>
    <property name="hibernate.connection.username">sa</property>
    <property name="hibernate.connection.password"></property>
    <property name="hibernate.dialect">net.sf.hibernate.dialect.HSQLDialect</property>
    <property name="show_sql">true</property>
    <property name="transaction.factory_class">
         net.sf.hibernate.transaction.JDBCTransactionFactory
    </property>
    <!--성능관련 element  이하 property 3개-->
<property name="hibernate.cglib.use_reflection_optimizer">false</property>
<property name="hibernate.cache.provider_class">
        net.sf.ehcache.hibernate.Provider
</property>
    <mapping resource="vssh/model/BlogItem.hbm.xml"/>
    <mapping resource="vssh/model/Blog.hbm.xml"/>
  </session-factory>
</hibernate-configuration>


앞에서 보면 property 엘리먼트를 이용해서 데이터베이스 연결 정보를 직접 설정하고 있다. 물론 이것은 커넥션 풀을 사용할 경우 그에 대한 JNDI 이름으로 대체될 수 있다. hibernate.dialect 부분에는 데이터베이스별로 고유의 클래스를 적당히 입력해 줘야 한다. 하이버네이트가 지원하는 수많은 데이터베이스별로 각각의 dialect 값이 미리 결정되어 있다.

하이버네이트는 이 dialect를 통해 HQL(Hibernate Qurery Language)을 각 벤더별 SQL 문으로 변환시킨다. 각 데이터베이스별 dialect 정보는 여기를 참고하기 바란다. Property 중에서 show_sql이 true로 설정되면, 콘솔창에 각 벤더별로 하이버네이트 Dialect에 의해 변형된 SQL 문을 출력하게 된다. 그 아래에 있는 transaction.factory_class는 하이버네이트에서 자바 가상머신 수준의 캐시를 사용하기 위해 각 WAS 벤더 별로 구현되어 있다.

앞의 Blog.hbm.xml, BlogItem.hbm.xml, hibernate.cfg.xml의 성능 관련 element라고 주석으로 표기된 부분을 보기 바란다.


hibernate.cfg.xml
<hibernate-configuration>
<!--성능관련 element  이하 property 3개-->
<property name="hibernate.cglib.use_reflection_optimizer">false</property><!-(1)-->
<property name="hibernate.cache.provider_class"><!-(2)-->
        net.sf.ehcache.hibernate.Provider
</property>
</hibernate-configuration>


(1)은 CGLIB의 리플렉션 옵티마이저의 사용 유무를 결정짓는 것이다. 리플렉션을 이용하게 되면 디버깅 등에 유리 할 수도 있으나, 이를 쓰지 않는 것이 성능에 도움이 된다.

(2)는 cache provider를 설정하는 곳으로 하이버네이트의 경우 플러그인 형태로 존재한다. 설정된 EHCache가 기본 값인데 이 밖에도 Hashtable, OSCache, SwarmCache, JBoss TreeCache 등이 있으며 각각의 캐시를 쓰기 위해서는 Provider class를 기술해야 한다. 또 EHCache의 경우 classpath가 잡힌 곳에 다음과 같이 ehcache.xml를 작성해 줘야 한다.

ehcache.xml 파일의 루트 엘리먼트는 enhance로 defaultCache와 cache를 서브 엘리먼트로 갖고 있다. 다음의 경우 defaultCache를 통해 모든 퍼시스턴스 객체에 대해 적용할 캐시 룰을 정의하고, vssh.model.Blog 클래스에 대해서는 다른 캐시 설정을 해두었다. 캐시의 사용 옵션은 hbm.xml 파일에서 정의하게 된다. 지금까지 작성된 예제는 캐시의 사용 옵션 중 read-write를 사용했다. 그 밖의 옵션으로는 transactional, nonstrict-read-write, read-only가 있다. 하이버네이트 성능 향상 기법, Second Level Cache를 이용한 하이버네이트 성능 향상 링크를 참조하도록 하라.


ehcache.xml
<ehcache>
  <defaultCache maxElementsInMemory="10000" eternal="false"
      timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
        />
  <!-- for vssh.model.Blog -->
  <cache name="vssh.model.Blog" maxElementsInMemory="10000" eternal="false"
      timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true"
      />        
</ehcache>


hibernate.cfg.xml 파일은 클래스패스가 잡힌 곳에 두면 된다. 그리고 log4j를 사용할 경우, log4j.xml 또한 같은 위치에 두면 기본적으로 사용이 가능하다. Log4j가 설정되어 있지 않으면 하이버네이트는 경고 메시지를 계속해서 보여주기 때문에, log4j는 꼭 설정해서 하이버네이트와 함께 사용하는 것이 좋다. 위의 내용을 이클립스와 관련 플러그인, xDoclet 등을 이용해서 처리하는 과정은 VSSH 포럼에 자세히 기록해 두었다. 툴을 이용하는 방법에 대해 관심이 있는 분은 포럼을 이용하시기 바란다. eclipse.new21.org에서도 관련 정보를 찾아볼 수 있다.

4단계. 하이버네이트로 데이터베이스 값 가져오기
이제 마지막으로 하이버네이트를 이용해 데이터베이스에 기록된 값을 가져와 보자. 데이터베이스 조작을 위해 사용하는 핵심 클래스는 SessionFactory이다. SessionFactory는 hibernate.cfg.xml에서 설정된 하나의 데이터베이스와 연결을 하게 된다. 웹으로 포팅하는 경우, SessionFactory는 웹 컨테이너가 시작될 때 한번만 기동되며 이때 모든 정보가 로드된다. 만일 여러 종류의 데이터베이스에 연결을 하고자 한다면 hibernate.cfg.xml에 연결하고자 하는 데이터베이스의 수만큼 SessionFactory를 기술해야 할 것이다.


BlogMain.java
private SessionFactory sessionFactory;
public void configure() throws HibernateException {
sessionFactory = new Configuration().configure()
.buildSessionFactory();
}
public Blog getBlogAndAllItems(Long blogid) throws HibernateException {
    Session session = sessionFactory.openSession();
    Transaction tx = null;
    Blog blog = null;
    try {
        tx = session.beginTransaction();
        // (1) HQL을 바로 사용하기
        Query q = session.createQuery("from Blog as blog "
                + "left outer join fetch blog.items "
                + "where blog.id = :blogid");
        // (2) 이름으로 쿼리 정보 가져오기
        //Query q = session.getNamedQuery("model.Blog");

        q.setParameter("blogid", blogid);
        blog = (Blog) q.list().get(0);
        tx.commit();
    } catch (HibernateException he) {
        if (tx != null) tx.rollback();
        throw he;
    } finally {
        session.close();
    }
    return blog;
}
...<중략>...


앞의 예제는 BlogMain 클래스로 SessionFactory를 이용하는 방법을 보여준다. Session을 이용하는 과정이 JDBC에서 connection을 이용하는 방법과 유사함을 확인할 수 있다. 그리고 Transaction은 session을 통해 가져오는데, begin 및 commit, rollback하는 과정 역시 유사한 것을 볼 수 있다.

앞의 예제에서 주목할 곳은 (1)에 주석 처리된 HQL의 사용이다. 참고로 쿼리 자체를 *.hbm.xml 안에 입력해두고, 이름으로 쿼리 정보를 가져오는 방식도 적용 가능하다. 다음의 예제는 쿼리 정보를 XML 문서에 입력한 샘플 코드이다. 쿼리 정보를 XML 문서에 입력하는 방식(Named SQL Query)을 이용할 경우 (2)와 같은 코딩이 가능하다.


Blog.hbm.xml
... DTD 생략 ...
<hibernate-mapping package="vssh.model">
  <class name="Blog" table="BLOG lazy="true">
   ...중략...
  </class>                      
  <query name="model.Blog">      
    <![CDATA[                  
      from Blog as blog left outerin fetch blog.items where blog.id = :blogid
    ]]>                        
  </query>                       
</hibernate-mapping>


하이버네이트에서 사용하는 HQL은 EJB QL과 마찬가지로 객체지향적인 성격을 가진다. HQL은 앞에서 설정한 Dialect에 의해 각 벤더별로 최적화된 SQL 문으로 자동 변환된다.


HQL과 Dialect에 의해 변형된 SQL  
HQL
from Blog as blog
left outer join fetch blog.items
where blog.id = 1

앞의 HQL은 hiber8ide를 통해 값을 확인할 수 있다.

                  HQL이 hibernate.dialect를 통해 각 벤더에 맞는 SQL로 변환


HSQL의 SQL
select blog0_.BLOG_ID as BLOG_ID0_,
  items1_.BLOG_ITEM_ID as BLOG_ITE1_1_,
  blog0_.NAME as NAME0_,
  items1_.TITLE as TITLE1_,
  items1_.TEXT as TEXT1_,
  items1_.DATE_TIME as DATE_TIME1_,
  items1_.BLOG_ID as BLOG_ID1_
from BLOGS blog0_ left outer join BLOG_ITEMS items1_ on blog0_.BLOG_ID=items1_.BLOG_ID
where (blog0_.BLOG_ID=1 )


앞의 HSQL Databse Manager를 통해 SQL을 확인해 볼 수 있다. 이클립스의 하이버네이트 플러그인인 Hiber8IDE를 사용하면 HQL 결과를 보는 것이 가능하고, JOI(Java Object Inspector)라는 툴을 이용하면 컬렉션의 내부도 확인할 수 있다. 이 곳을 참고하도록 하라.

<그림 5> Hibern8IDE와 JOI


이것으로 하이버네이트에 대한 소개를 마치도록 하겠다. 하이버네이트는 관련 자료가 비교적 풍부하기 때문에, 자세한 내용은 참고자료를 통해 스스로 익혀나가길 바란다.

스프링과 하이버네이트 연동하기
이제 지난 글에서 소개한 고객 등록 예제를 통해 스프링과 하이버네이트가 어떻게 연동되는지에 대해 살펴보도록 하자. 고객 등록 예제는 스프링 사용 시나리오 중에서 써드파티 WAF와 OR 맵핑 도구를 연계한 웹 애플리케이션 형태이다.

예제는 프리젠테이션 계층과 제어 계층을 포함하는 웹 계층의 처리를 위해 스트럿츠를 사용하고 있으며, OR 맵핑 도구로 하이버네이트를 채택해서 퍼시스턴스 계층을 담당하도록 하고 있다. 스트럿츠와 하이버네이트를 연결하는 위치에서 애플리케이션 로직을 체계적으로 관리하기 위해 스프링이 사용되는 것이다.

<그림 6> 써드파티 WAF와 ORM을 연계한 웹 애플리케이션 구조의 스프링 사용 시나리오


예제에 대한 기본 설명과 스트럿츠 연동 부분은 지난 글에서 다뤘으므로 여기서는 조금 더 상세하게 전체적인 관점에서 살펴보도록 하자.

UML 다이어그램으로 살펴보는 고객 등록 예제
우선 <그림 7>에 소개된 패키지 다이어그램을 주목해 주기 바란다.

<그림 7> 고객 등록 예제의 패키지 다이어그램


고객 등록 예제는 기본적으로 4개의 패키지가 주축을 이룬다. Model 패키지에는 하이버네이트에서 OR 맵핑 정보를 기록하는 데 사용되는 hbm.xml 파일과 hbm2java 태스크를 이용해서 생성된 모델용 클래스를 포함한다. DAO 패키지에는 하이버네이트를 이용해서 데이터 처리를 수행할 인터페이스와 그 구현 객체가 포함되어 있다.

Business 패키지에는 DAO 객체를 이용해서 필요한 비즈니스 로직을 수행하는 비즈니스 델리게이트가 포함되어 있으며, 그 결과는 Model 패키지에서 정의된 모델을 통해 전달된다. Control 패키지에는 스트럿츠의 액션 클래스가 정의되어 있으며, Business 패키지에서 정의된 델리게이트 객체를 이용해서 사용자 요청을 처리한다.

각 패키지에 포함된 주요 클래스들의 클래스 다이어그램은 <그림 8>에 정리되어 있다.

<그림 8> 고객 등록 예제의 클래스 다이어그램


<그림 9>는 사용자가 고객 리스트를 요청했을 때 처리되는 과정을 시퀀스 다이어그램으로 표현한 것이다.

<그림 9> 고객 리스트를 요청했을 경우의 시퀀스 다이어그램


hibernate.cfg.xml를 대체하는 스프링의 applicationContext.xml
하이버네이트만을 사용하는 블로그 예제에서 hibernate.cfg.xml을 이용해서 데이터베이스 연결 정보와 OR 맵핑 정보들을 설정해 준 것을 기억할 것이다. 스프링과 하이버네이트를 통합해서 사용할 때는 hibernate.cfg.xml을 작성할 필요가 없다. 스프링의 기본 설정 파일인 applicationContext.xml에 빈(bean) 설정을 통해 해결하기 때문이다. 다음의 코드는 DataSource와 SessionFactory를 IoC 적인 특징을 갖는 스프링의 빈 팩토리 설정 방식으로 해결하는 과정을 보여준다.


applicationContext.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>
   <property name="url"><value>jdbc:hsqldb:data/vsshdb</value></property>
   <property name="username"><value>sa</value></property>
   <property name="password"><value></value></property>
</bean>
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
   <property name="dataSource"><ref local="dataSource"/></property>
   <property name="mappingResources">
      <list>
         <value>model/Customer.hbm.xml</value>
      </list>
   </property>
   <property name="hibernateProperties">
   <props>
      <prop key="hibernate.dialect">net.sf.hibernate.dialect.HSQLDialect</prop>
          <!-- http://www.hibernate.org/194.html -->
          <prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
         <prop key="hibernate.hbm2ddl.auto">create</prop> (1)
      </props>
   </property>
</bean>


JDBC 드라이버와 URL, 아이디, 패스워드로 구성되는 데이터 소스 설정은 쉽게 이해할 수 있을 것이다. 하이버네이트의 세션 팩토리가 어떻게 스프링에서 설정되는지에 대해서 조금 더 자세히 살펴보자.

하이버네이트 세션 팩토리는 스프링의 ORM 패키지 중 하이버네이트와의 연동을 위한 LocalSessionFactoryBean 클래스에 의해 처리된다. 이 빈은 DataSource, mappingResources, hibernateProperties의 3가지 속성을 필요로 한다. 다른 설정 값은 hibernate.cfg.xml에서 보았던 것이므로 설명을 생략한다. 소스코드 중 (1)로 표기된 부분은 애플리케이션이 시작될 때 자동으로 스키마의 DDL 문을 익스포트(export)하라는 설정이다.

웹 애플리케이션에서 스프링의 applicationContext.xml의 설정을 반영하도록 하려면 web.xml에 다음과 같은 추가 정보를 입력하면 된다.


web.xml
<context-param>
  <param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext1.xml,applicationContext2.xml
</param-value>
</context-param>


만일 예제와 같이 스트럿츠와 스프링을 함께 사용하는 경우라면 스트럿츠의 기본 설정 파일인 struts-config.xml에 ContextLoaderPlugIn을 이용해 다음과 같은 플러그인 설정을 추가로 입력한다.


struts-config.xml
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation"
        value="/WEB-INF/applicationContext.xml,
               /WEB-INF/action-servlet.xml"/>
</plug-in>



스프링과 하이버네이트 연동을 위한 개발 팁!
1. HibernateTemplate을 사용하자
다음의 코드는 DAO 구현 중 고객 리스트를 가져오는 부분이다. 첫 번째 코드는 하이버네이트를 사용하는 일반적인 방식으로 구현되어 있다. 세션 팩토리를 이용해서 세션을 생성한 다음, 생성된 세션을 이용해서 고객 리스트를 가져오고 있다. 처리 과정 중에 발생하는 예외에 대한 처리도 이루어지고 있음을 볼 수 있을 것이다.

 <리스트 1> 스프링에서 하이버네이트 기능을 사용하는 기본적인 코드

스프링에서는 이러한 복잡한 과정을 DAO 패턴을 이용해서 감춰주는 HibernateTemplate 클래스를 제공한다. HibernateTemplate을 이용하면 다음과 같이 단 한 줄의 코드로 원하는 결과를 얻을 수 있다.

 <리스트 2> 스프링의 HibernateTemplate를 사용해서 복잡한 코드 감추기

2. IoC에 대한 개념 이해를 확실히 하자
비즈니스 델리게이트로 사용되는 CustomerBizManagerImpl 클래스의 소스코드를 살펴보면, 사용하는 DAO 클래스에 대한 인스턴스를 만드는 코드가 없는 것을 발견할 수 있다. 대신 setCustomerDAO()라는 메쏘드를 구현하고 있다.


CustomerBizManagerImpl.java
public class CustomerBizManagerImpl implements ICustomerBizManager {
private ICustomerDAO dao;
public void setCustomerDAO(ICustomerDAO dao) {
    this.dao = dao;
}    
  ...생략...


스프링에서 각 클래스간의 연관 관계는 코드에서 직접 드러나는 게 아니라, applicationContext.xml에서 기록된 빈 설정을 이용해서 처리된다. 각 클래스가 필요로 하는 인스턴스를 자신이 직접 결정하는 게 아니라 설정 정보에 따라 IoC 컨테이너인 스프링에 의해 결정되어 각 클래스에 있는 setter 메쏘드를 이용해서 거꾸로 연결되는 것(Setter Injection)이다. 이러한 제어의 반전(Inversion of Control)이 스프링에서는 늘 일어나기 때문에 지난 글에 소개한 IoC에 대한 개념을 확실히 이해하고 있는 것이 좋다.


applicationContext.xml
<bean id="customerDAO" class="dao.CustomerDAOImpl">
    <property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>


3. 트랜잭션 처리 과정을 자동화하자.
퍼시스턴스 계층을 구현하는 데 있어 가장 중요하게 여기는 부분 중 하나는 트랜잭션 처리이다. 스프링을 이용하면 비즈니스 델리게이트에서 하이버네이트로 구현된 DAO를 호출할 때 마치 EJB에서 CMT(Container Managed Transaction)를 사용하는 것처럼 트랜잭션 처리 과정을 자동화할 수 있다.


applicationContext.xml
<bean id="customerBizManager"
    class="org.springframework.transaction.interceptor
.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref local="transactionManager"/></property>
    <property name="target"><ref local="customerBizManagerTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="create*">PROPAGATION_REQUIRED</prop>
        <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>    
<bean id="transactionManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
   <property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="customerBizManagerTarget" class="business.CustomerBizManagerImpl">
    <property name="customerDAO"><ref local="customerDAO"/></property>
</bean>    


앞의 예제를 보면 customerBizManager라는 id를 가지는 빈에서 트랜잭션 처리가 설정되어 있음을 확인할 수 있다. customerBizManager 빈의 구현 클래스는 TransactionProxyFactoryBean으로서 이 클래스의 API를 참조하면 필요한 속성 정보를 확인할 수 있다.

transactionManager 속성은 스프링의 ORM 패키지의 하이버네이트 확장 기능 중 HibernateTransactionManager를 사용하고 있다. <그림 10>은 스프링의 트랜잭션 관련 클래스들을 정리한 것이다. 트랜잭션 매니저는 PlaformTransactionManager 인터페이스를 구현한 서브 클래스 중 하나를 선택해주면 된다. 예를 들어 JDO의 아파치 구현인 OJB를 OR 맵핑 도구로 사용하고 있다면, JdoTransactionManager를 이용하면 된다.

<그림 10> 스프링의 트랜잭션 관련 클래스들


설정된 트랜잭션 기능이 사용될 타겟 클래스는 business 패키지에 있는 CustomerBizManagerImpl 클래스가 설정되어 있다. transactionAttributes 속성에는 CRUD시에 필요한 트랜잭션 처리를 다음 값 중 하나로 설정해주면 된다. 트랜잭션 속성은 모두 6가지 레벨을 지원한다. 좀 더 자세한 내용은 이 곳에서 확인할 수 있다.


◆ PROPAGATION_REQUIRED

◆ PROPAGATION_SUPPORTS

◆ PROPAGATION_MANDATORY

◆ PROPAGATION_REQUIRES_NEW

◆ PROPAGATION_NOT_SUPPORTED

◆ PROPAGATION_NEVER


오픈소스로 개발 문화를 바뀌자!
이것으로 애플리케이션 계층별로 경쟁력 있는 오픈소스 프레임워크를 선정하고, 각각의 프레임워크의 장점을 살려 약한 결합도를 가지는 통합 프레임워크를 구성하는 방법에 관한 연재글을 마치게 되었다.

이 연재는 프리젠테이션 계층에는 템플릿 엔진인 벨로시티를, 제어 계층에는 Service to Worker 패턴이 구현된 스트럿츠를, 애플리케이션 로직 계층에는 AOP(Aspect Oriented Programming)와 IoC의 개념이 결합된 경량급 컨테이너인 스프링을, 퍼시스턴스 계층에는 OR 맵핑 도구의 선두 주자인 하이버네이트를 선정해 진행되었다. 너무 많은 기술들을 소개하다보니 어쩔 수 없이 각 프레임워크간의 결합에 대해 조금 더 깊이 있는 설명을 드리지 못한 점이 아쉬움으로 남는다.

필자들이 만든 VSSH라는 용어가 마치 하나의 트렌드처럼 자바 커뮤니티에서 사용되고 있는 것도 심심치 않게 발견할 수 있었다. 그럴 때면 오픈소스 프레임워크의 활용에 대해 많은 자바 개발자들이 관심을 가지도록 유도하고자 했던 최소한의 목적은 달성된 것 같아 다행스런 마음도 느낀다.

마이크로소프트웨어 2004년 10월호에 실린 「WAS 시장을 뒤흔든 오픈소스의 도전장」이란 기사에서 담당 기자는 "전 세계적으로 특정 벤더의 독점적인 제품들이 소프트웨어 시장을 지배하는 현재 상황에서 오픈소스는 자국 소프트웨어 산업의 자생력을 확보하기 위한 거의 유일한 대안이며, 기술적으로는 이미 그 가능성을 입증한 단계에 왔다고 할 수 있다"고 말했다. 필자들은 이번 연재를 진행하면서 오픈소스가 우리네 기술 수준을 한 단계 향상시킬 뿐 아니라 충분히 실무에서 활용될 수 있는 수준에 도달했음을 느낄 수 있었다.

오픈소스에 관한 부정적 편견과 각 기술을 숙련되게 익히고 있는 개발자의 부족 문제만 해결된다면 오픈소스를 이용해서 우리네 개발 문화를 획기적으로 개선하는 것도 가능할 것이다

 

출처 : 마소 2004.11

소스 : 다운로드

 

오픈소스를 이용한 시스템 통합 그 세번째 이야기입니다..*^^*

 

1회, 오픈소스를 이용한 시스템 통합, 그 즐거운 도전의 시작
2회, 첫번째 도전! 스트럿츠와 벨로시티의 통합

3회, 약한 결합도 아키텍처를 위한 대안 기술, 스프링
4회, OR 맵핑 도구의 선두 주자, 하이버네이트

 

지난 글에서는 오픈소스 프레임워크를 통합하기 위한 첫 번째 시도로 벨로시티와 스트럿츠가 어떻게 연동될 수 있는지를 살펴보았다. 이번 글에서는 VSSH 구성을 위한 핵심 기술인 스프링 프레임워크에 대해 살펴볼 것이다.

스프링을 통해 최근 자바 커뮤니티의 관심이 집중되고 있는 IoC 컨테이너와 AOP에 대한 개념을 접해보도록 하자. 그리고 스프링을 스트럿츠와 연동하기 위해 필요한 절차에 대해서도 알아보자.

자바를 이용해 기업용 비즈니스 시스템을 구축하는 것은 보통 일이 아니다. 개발자들은 복잡도를 낮추기 위해 MVC 패턴이 녹아있는 n-계층 C/S 환경을 구성하기 시작했으며, 점차 시간이 흘러가면서 대규모 웹 애플리케이션은 다음의 다섯 가지 계층으로 일반화되어 적용되고 있다.


◆ 프리젠테이션 계층(Presentation Layer)

◆ 제어 계층(Control Layer)

◆ 비즈니스 로직 계층(Business Logic Layer)

◆ 퍼시스턴스 계층(Persistence Layer)

◆ 도메인 모델 계층(Domain Model Layer)


다양한 프로젝트를 경험하면서 자바 커뮤니티는 최상의 실천 사례들을 디자인 패턴의 힘을 빌어 J2EE/EJB 패턴으로 구체화시켜 나갔다. 그 중 가장 널리 알려진 것은 코어 J2EE 패턴과 EJB 패턴이다. 개발자들은 J2EE 기술의 장점을 최대한 살리기 위해서는 패턴에 기반한 아키텍처의 구성이 무엇보다 중요하다는 사실을 깨닫기 시작했으며, 그 아키텍처의 구현인 WAF(Web Application Framework)들이 수없이 많이 쏟아져 나오기 시작했다.

WAF들의 춘추전국 시대는 스트럿츠와 웹워크(WebWork)에 의해 일단락 된 듯하다. 우리나라의 경우 스트럿츠는 실질적인 웹 애플리케이션 프레임워크의 표준으로 자리잡았다고 볼 수 있다.

많은 개발자들은 스트럿츠를 사용함으로써 좋은 프레임워크가 어떤 코드가 어디에 위치되어야 하는지를 알려주는 가이드라인을 형성해 주며, 자연스럽게 좋은 설계를 이끌어내는 장점이 있음을 알게 되었다. 또한 좋은 설계를 위해 필수적인 기본 패턴들이 미리 구현되어 있어 훨씬 더 중요한 애플리케이션 로직 자체에 집중할 수 있는 여유를 준다는 점도 깨달았다.

최근 들어 자바 커뮤니티는 엔터프라이즈 영역에서 주류를 이루던 J2EE 기술에 대한 대안적 기술들의 등장에 많은 관심을 쏟고 있다. 그 대부분은 특정 업체의 주도가 아닌 개발자들의 경험을 바탕으로 하는 창조적인 아이디어에 기반한 것이며 오픈소스 형태로 진행 중이다.

뿐만 아니라 J2EE 기술 자체도 JSP 2.0과 JSF(Java Server Faces), 그리고 EJB 3.0, 타이거(J2SE 5.0)라는 큰 변화를 앞두고 있는 형편이다. 이처럼 다양한 대안적 기술들이 존재하는 상황에서 우리는 각 계층을 구현하기 위해 어떤 기술을 선택하고, 또 각각을 어떻게 하나의 아키텍처로 통합해야 하는 것일까?

이러한 궁금증을 가져본 독자들이라면 마크 이글의 글(오픈소스 자바 프로젝트를 응용한 웹 애플리케이션 개발)을 한번쯤 읽어볼 필요가 있다. 그는 아키텍처를 구성하는 각각의 계층들이 애플리케이션 내에서 명확히 구별되는 기능을 가지고 있으며, 서로 다른 계층을 침범하거나 그 기능에 있어 중복되는 점이 없어야 한다고 주장한다. 그의 글을 토대로 각 계층에서 제공해야 하는 기능은 무엇인지, 또한 제공하지 말아야 할 기능은 무엇인지를 간단히 살펴보도록 하자.

프리젠테이션 계층
◆ 역할 : 프리젠테이션 계층은 말 그대로 사용자 인터페이스에 불과하다. 식당을 예로 들면 손님이 접하게 되는 메뉴판과 전달될 음식을 차려놓는 식탁에 해당한다.

◆ 기능 : 사용자가 선택할 수 있는 기능이 표시되어 있어야 하고, 요청에 필요한 부가적인 정보 전달을 위한 입력 양식이 있어야 한다. 또한 전달된 자료를 효과적으로 보여주기 위한 프리젠테이션 로직이 포함된다. 하지만 비즈니스 로직이나 퍼시스턴스 계층에서 처리하는 일을 직접 수행하거나(스크립트 릿 사용), 각 계층의 컴포넌트와 직접적인 통신이 있어선 안된다.

모든 요청은 제어 계층을 통해 처리되어야 한다는 뜻이다. 고급 레스토랑(엔터프라이즈 시스템)에서는 웨이터(지배인)를 통해서만 요구를 전달하고, 그 결과를 전해들어야 한다. 직접 주방장에게 주문을 하거나 자기가 직접 요리를 하는 것은 자기 집(프로토타입)이나 동네 자장면 가게(소규모 웹 애플리케이션)에서나 가능한 일이다.

◆ 대안 기술 : 현재 가장 주류를 이루는 기술은 JSP 1.2와 JSTL과 같은 태그 라이브러리를 결합하는 방식이다. 과도기적인 형태로 벨로시티와 타일즈 태그 라이브러리가 결합된 형태도 현재 주목을 받고 있다. 하지만 점차적으로 JSF에 기반한 JSP 2.0에 주류 기술로 옮겨갈 가능성이 크고, 프리젠테이션 계층 개발에 있어서도 JSF를 지원하는 IDE를 채택하는 경우가 늘어날 것이다.

◆ 주요 패턴 : Composite View 패턴

제어 계층
◆ 역할 : 제어 계층은 프리젠테이션 계층과 비즈니스 로직 계층을 분리하기 위한 컨트롤러를 제공한다. 식당으로 치자면 지배인의 역할과 종업원의 역할을 병행하는 것이라고 볼 수 있다.

◆ 기능 : 전체 시스템의 설정 상태를 유지해야 하며, 그를 통해 어떤 요청이 들어왔을 때 어떤 로직이 처리해야 하는지를 결정한다. 사용자 요청을 검증하고 로직에 요청을 전달하는 일과 로직에서 전달된 응답을 적절한 뷰에 연결짓는 것 역시 제어 계층의 몫이다.

손님이 바다가재 요리를 요청했을 때 종업원은 그 요리가 서비스 가능한 것인지 또한 누구에게 시키면 되는지를 알고 있어야 한다는 뜻이다. 요청을 전달받은 요리사가 바다가재 요리를 주면 그것을 식탁까지 운반해 주는 것 역시 종업원의 몫이다. UI 검증, 요청 및 응답 전달, 로직에서 던져진 예외 처리, 도메인 모델을 뷰와 연결하기 등의 고유 기능 외에는 어떤 기능도 포함하지 않는다.

◆ 대안 기술 : 현재 WAF들은 대부분 제어 계층의 핵심 기능을 포함한다. 터빈이나 에스프레소 등이 선전하고 있지만, 스트럿츠와 웹워크가 대세라고 생각된다. 당분간 별다른 대안 기술이 등장할 가능성은 적다. 오히려 스트럿츠를 확장시켜 자사 고유의 프레임워크로 최적화 시키는 작업이 활발히 진행될 것이다.

◆ 주요 패턴 : Front Controller 패턴, Service to Worker 패턴(또는 Command 패턴), Intercepting Filter 패턴, Application Controller & Context Object 패턴

비즈니스 로직 계층
◆ 역할 : 비즈니스 로직은 말 그대로 핵심 업무를 어떻게 처리하는지에 대한 방법을 기술하는 곳이다. 식당에서 종업원이 고객의 요구를 전달해 주면, 재료를 이용해 요리를 만드는 요리사라고나 할까? 비즈니스 로직 계층은 애플리케이션에서 가장 재사용될 확률이 높은 요소이기 때문에 신경 써서 설계해야 한다.

◆ 기능 : 비즈니스 로직에는 핵심 업무 로직의 구현과 그에 관련된 데이터의 적합성 검증 외에도 다양한 부가적인 구현이 추가된다. 트랜잭션 처리라든가, 다른 계층들과 통신하기 위한 인터페이스를 제공한다거나, 해당 계층의 객체들간의 관계를 관리하는 것 등이 그것이다.

비즈니스 로직 계층에 있어야 할 코드들이 프리젠테이션 계층이나 퍼시스턴스 계층에 여기저기 흩어져 있는 애플리케이션을 찾아보기란 그리 어려운 일이 아니다. 이런 구조는 각각의 계층을 모호하게 만들어 유지보수시 많은 시간을 필요로 하게 만든다.

가장 신경 써서 개발해야 할 비즈니스 로직에 그동안 신경을 쓰지 못했다는 것. 프리젠테이션 계층과 퍼시스턴스 계층 사이의 다리 역할을 충실히 하도록 함으로써 애플리케이션에 유연성을 더하는 것. 그것이 스프링이 탄생하게 된 배경이라고 할 수 있다.

◆ 대안 기술 : 지금까지 비즈니스 로직의 구현은 크게 EJB를 사용하는 것과 일반 자바 객체(POJO)를 사용하는 것으로 나눌 수 있었다. EJB를 사용하는 경우 개발자들의 많은 불만이 EJB 3.0을 사용함으로써 해결되리라 예상된다. 하지만 해외를 중심으로 해서 EJB를 사용하건, 사용하지 않건 비즈니스 로직들을 체계적으로 관리하는 IoC 컨테이너에 대한 관심이 증가하고 있는 추세이다. IoC 컨테이너에 대해서는 뒤에서 다시 자세히 살펴보도록 하겠다.

◆ 주요 패턴 : Business Delegate 패턴, Session Facade 패턴, Service Locator 패턴, Application Service 패턴, EJB Home Factory 패턴

퍼시스턴스 계층
◆ 역할 : 퍼시스턴스 계층은 데이터 처리를 담당하는 계층이다. 주로 데이터의 생성/수정/삭제/선택(검색)과 같은 CRUD 연산을 수행하게 된다. 식당으로 보자면 주방장이 사용할 재료를 담당하는 재료 담당자라고나 할까? 이 데이터는 주로 데이터베이스에서 처리되는 경우가 많아, 영속성을 의미하는 퍼시스턴스 계층이란 용어를 사용했다. 하지만 데이터가 처리되는 다른 업무 시스템이나, 웹 서비스, XML, 파일 시스템 등을 모두 고려한다면 레거시 개념을 갖는 EIS 계층이란 표현이 더 적합할 것이다.

◆ 기능 : 이 계층에서 수행하는 일은 관계형 정보를 저장하고, 수정/삭제하는 것과, 그러한 일을 수행하는 데 필요한 질의문을 관리하는 것, 그리고 가져온 관계형 정보를 객체화시키는 일이다.

◆ 대안 기술 : EJB 사용에 있어서 개발자들이 가장 불만스러워 하는 것은 CMP 방식의 엔티티 빈일 것이다. 그러한 불만은 객체 관계 맵핑(ORM)을 이용한 JDO라는 대안기술을 탄생시켰고, 또한 하이버네이트라는 또 하나의 오픈소스 기술을 실무로 끌여들였다. JDBC를 이용한 DAO 객체를 구성하는 방법도 소규모 애플리케이션에서는 여전히 인기를 끌고 있다. EJB 3.0과 JDO/하이버네이트, 그리고 JDBC를 이용한 POJO 방식 중 어떤 것이 개발자들에게 낙점될 지는 아직 미지수다.

◆ 주요 패턴 : Data Access Object 패턴, Domain Store 패턴, Sequence Blocks

도메인 모델 계층
◆ 역할 : 도메인 모델은 각 계층 사이에 전달되는 실질적인 비즈니스 객체라고 할 수 있다. 식당을 예로 든다면, 음식이 담긴 그릇이라고 비유할 수 있겠다.

◆ 기능 : 도메인 모델 계층은 흔히 데이터 전송 객체(DTO) 형태로 개발자가 직접 제작해서, 리퀘스트나 세션과 같은 컨텍스트에 담아 넘기게 된다. 하지만 데이터베이스의 모든 정보를 일일이 객체로 만드는 것은 귀찮을 뿐 아니라, 계층간의 통신 과정에서 데이터가 유실될 위험도 있기 때문에 최근에는 도메인 모델을 서비스로 제공하여 자동화하는 경우가 많다.

◆ 주요 패턴 : Data Transfer Object 패턴, Value List Handler 패턴

지금까지 엔터프라이즈 시스템을 구축하기 위해 일반적으로 사용되는 다섯 계층에 대해 간단히 정리해 보았다. 뻔한 이야기들을 길게 늘여 쓴 이유는 2가지 사실을 강조하기 위해서이다. 첫째, 각각의 계층은 저마다의 분명한 역할이 존재하며, 그 역할을 충실히 수행할 수많은 대안기술(Alternative)들 사이에서 개발자는 무엇을 선택할 지 결정을 내려야 한다. 둘째, 각각의 기술들은 독립적으로도 충분한 가치를 지니고 있지만, 가장 장점을 발휘하는 제 위치에서 서로 연계되어 사용될 때 그 시너지 효과가 더욱 크다.

약한 결합도를 가진 아키텍처 구성
이 글을 읽는 상당수의 독자들은 애플리케이션을 개발할 때 스트럿츠를 사용해 본 경험이 있을 것이다. 프리젠테이션 계층에는 태그 라이브러리가 접목된 JSP를 이용했을 것이며, 업무 로직과 영속성 처리는 그 규모에 따라 POJO나 EJB를 적절히 섞어 사용했을 것이다. 하지만 정말 스트럿츠만으로 충분했었는지 묻고 싶다.

<그림 1> 아키텍처를 구성하는 다섯 계층과 대안 기술


<그림 1>은 앞에서 정리한 각 계층을 도식화하고, 각 계층별 대안 기술을 요약해서 표기한 것이다. 언뜻 보기에 전체 구조는 흠잡을 곳이 없어 보이기도 한다. 그렇다면 다음의 3가지 질문에 대답해 보자.

[1] 제어 계층에서 비즈니스 로직 계층에 구현된 기능을 이용하기 위해서는 구체적인 설정 정보를 알아야 한다. 그런데 만일 이용하고자 하는 기능에 대한 설정이 변경된다면 어떻게 할 것인가? 컴포넌트의 설정과 그 사용을 분리할 수 있는 방법은 과연 무엇일까(힌트 : 리팩토링의 저자이기도 한 마틴 파울러는 그의 홈페이지에서 이에 대한 해결책으로 제어 역행화(Inversion of Control) 패턴이란 것을 소개하고 있다)?

[2] 비즈니스 로직 계층에는 순수한 업무 로직의 구현 외에도 보안, 인증, 로그와 같은 시스템 전반에 걸친 기능들이 공존한다. 이러한 기능은 업무 로직과 뒤섞여 코드를 관리하기 어렵게 하는 주범이 되기도 한다. 이에 대한 해결책은 없는가(힌트 : AOP는 바로 그러한 문제를 다루기 위한 새로운 패러다임이다)?

[3] 지금 엔터프라이즈 자바 영역은 표준화된 주류 기술과 창의적인 다양한 아이디어로 무장한 오픈소스 기술들로 인해 수없이 많은 선택을 개발자들에게 강요하고 있다. 지금 현재는 대안들 중 하나를 선택했더라도 시간이 지남에 따라 그 선택을 바꾸게 될 가능성도 얼마든지 존재하는 것이다.

스트럿츠를 웹워크로 교체하더라도 또는 EJB 구현을 하이버네이트로 교체하더라도 그러한 변경의 영향이 전체에 파급되지 않고, 해당 계층에 국한되도록 할 수 있는 보다 포괄적인 프레임워크는 없을까(힌트 : 이러한 기능을 수행하는 프레임워크를 경량급 컨테이너(Lightweight Container)라고 부른다. 경량급 컨테이너의 핵심 원리가 바로 제어 역행화 패턴이다. 스프링 프레임워크는 경량급 컨테이너임과 동시에 AOP를 지원한다. 이것이 우리가 스프링(Spring)에 관심을 가지는 이유이다)?

각 계층별 결합도를 떨어뜨리는 것(loosely-coupled)은 좋은 아키텍처를 구성하기 위해 필수적인 요건이라고 할 수 있으며, 요즘처럼 대안 기술이 많을 경우에는 특히 중요하다고 하겠다. 약한 결합도를 가진 아키텍처를 구성하기 위한 핵심 기술이 바로 스프링 프레임워크이다.

스프링 프레임워크 개요
스프링은 그 이름 자체로도 많은 의미를 내포하고 있다. 봄! 이 얼마나 설레는 단어인가? 봄이라는 이름만으로도 무거운 J2EE의 사용으로 지친 개발자들에게 이제 겨울이 끝나고 새로운 계절이 돌아오고 있음을 함축적으로 표현해내고 있다. 스프링은 로드 존슨이 쓴 「Expert one-on-one J2EE Design and Development」란 책에서 소개된 소스코드를 기반으로 2003년 2월 오픈소스로 시작된 프로젝트이다. 스프링이 추구하는 바는 크게 두 가지이다.

[1] 복잡하고 무거운 J2EE 기술의 사용을 쉽고 가볍게 만들어주고, 자연스럽게 검증된 최상의 실천 사례들을 구현하도록 함으로써 좋은 프로그램이 작성될 수 있도록 유도한다.

[2] 기존의 잘 알려진 기술들을 프레임워크 내에서 일관된 방법으로 쉽게 사용할 수 있도록 돕는다.

이를 위해 스프링은 다른 프레임워크와는 차별화된 다음과 같은 특징을 가진다.

◆ 스프링은 EJB를 사용하건 하지 않건 관계없이 비즈니스 객체들을 효과적으로 구성하고, 관리하는 방법을 제공하는 데 초점을 맞춘다.

◆ 스프링은 계층화된 아키텍처를 갖고 있으며, 그 중 어떤 부분도 독립적으로 사용될 수 있도록 모듈화되어 있다. 뿐만 아니라 각각의 모듈은 일관된 방법으로 사용할 수 있기 때문에 한번 익숙해지고 나면 사용이 무척 쉽다.

◆ 스프링은 전체 프로젝트의 설정을 관리할 수 있는 일관된 방법을 제공함으로써, 개발자들이 각종 프로퍼티 파일을 작성하지 않도록 유도한다. 이것은 IoC라는 스프링의 특징 때문인데, 객체들간의 의존성이 따로 관리됨으로써 비즈니스 로직이 EJB로 개발되었건 일반 자바 객체로 개발되었건 동일한 방법으로 해당 로직을 이용할 수 있는 이점도 추가된다.

◆ 스프링 기반으로 작성된 애플리케이션은 스프링의 API에 의존하지 않는다. 이것은 어떤 애플리케이션 서버와도 쉽게 연동되도록 하며, 심지어 스프링을 사용하지 않았을 때조차도 비즈니스 로직의 재사용이 가능해지는 요인이 된다.

◆ 스프링은 AOP 지원을 통해 주요 비즈니스 로직과 시스템 전반에 걸친 기능 모듈을 완벽히 분리해내도록 도와준다.

◆ 스프링은 작성된 코드에 대한 유닛 테스트를 쉽게 할 수 있도록 도와준다.

스프링의 기능과 사용 시나리오
현재 스프링은 1.0 버전이 출시된 상태이다. 스프링 홈페이지(www.springframework.org)에서 spring-framework-1.0.2-with-dependencies.zip 파일을 다운받기 바란다. 이 파일은 의존성 있는 관련 라이브러리가 모두 포함된 버전이다. 스프링의 전체 기능은 크게 7개의 모듈로 구성된다(<표 1>).

<그림 2> 스프링의 기능 요소

 

<표 1> 스프링의 기능 요소


스프링 배포 파일의 압축을 풀면 dist란 디렉토리가 나타난다. 그 안에 있는 spring.jar가 앞에서 언급한 스프링의 모든 기능을 포함하는 파일이다. 각각의 기능 중 필요한 부분을 따로 사용할 경우를 위해 패키지별로 묶은 별도의 JAR 파일이 함께 제공된다.

스프링을 이용한 일반적인 형태의 웹 애플리케이션은 <그림 3>과 같은 구조를 가진다. 톰캣, 웹로직, 웹스피어, JBoss를 포함한 어떤 웹 애플리케이션 서버에서도 동작된다. IoC 컨테이너의 핵심인 Core 패키지와 AOP 지원을 위한 AOP 패키지, 기능을 구현한 빈 객체에 대한 접근을 제공하는 Context 패키지는 반드시 포함되어야 한다. 대부분 데이터베이스 처리를 수행하기 때문에 DAO 패키지도 일반적으로 포함된다.

스프링 애플리케이션은 일반적으로 ORM 솔루션을 채택하고, 특히 하이버네이트와 궁합이 잘 맞기 때문에 하이버네이트를 설치하고 ORM 패키지를 이용하는 경우가 일반적이다. 그 위에서 Web 패키지를 기반으로 Web MVC 패키지를 이용해서 애플리케이션을 개발한다.

<그림 3> 시나리오 1. 완전한 형태의 스프링 웹 애플리케이션


만일 스프링의 Web MVC 패키지를 이용하지 않고, 스트럿츠나 웹워크를 제어 계층에 사용하고 싶다면 Web MVC를 스트럿츠가 대체하는 <그림 4> 외에 같은 구조로 웹 애플리케이션이 개발될 것이다.

<그림 4> 시나리오 2. 서드파티 WAF와 ORM을 연계한 웹 애플리케이션


이 밖에도 EJB를 사용하는 경우 AbstractEnterpriseBean이라는 POJO를 이용해서 EJB를 스프링에서 관리하도록 하고, SlsbInvoker를 이용해서 EJB에 대한 접근 경로를 제공하는 EJB 사용 유형이 있다. 또한 웹 서비스를 포함한 다른 애플리케이션과 연동하는 경우를 위해 Remote 패키지가 제공되기도 한다.

스프링은 그 자체로도 한 권의 책을 쓸 수 있을 만큼 방대한 기술이다. 그 모두를 짧은 연재를 통해 소개한다는 것은 불가능하다. 그러한 이유로 스프링에 대한 소개는 이쯤에서 마무리하고, 스프링을 이해하기 위해 반드시 알아야 하는 IoC(Inversion of Control) 컨테이너의 개념과 AOP(Aspect Oriented Programming)를 간단히 설명하고, 2개의 작은 예제를 소개하는 것으로 이번 글을 마무리하겠다. 부족한 설명은 필자들이 운영하는 VSSH 포럼이나, 스프링 관련 기사 및 자료들을 정리한 리소스 맵을 통해 여러분 스스로 익혀나가기를 바란다.

IoC 컨테이너와 AOP
스프링은 다른 프로젝트에서 개발된 컴포넌트를 조립해서 응집력 있는 애플리케이션의 개발이 가능하도록 도와주는 IoC 컨테이너이며, 다른 말로 경량급 컨테이너(Lightweight Container)라고도 한다. IoC 컨테이너의 또 다른 종류로는 PicoContainer와 아파치의 아발론, 그리고 HiveMind 등이 있다.

그 중에서 현재 가장 널리 사용되고 있는 것은 스프링과 PicoContainer이다. IoC 컨테이너는 다른 컨테이너와 달리 애플리케이션 코드와 컨테이너간의 의존성을 최소화하는 것이 특징이다.

IoC 컨테이너가 컨테이너에 대한 의존성을 최소화하면서 컴포넌트를 엮어주는 일을 수행하는 밑바탕에는 제어 역행화(Inversion of Control)라는 개념이 깔려 있다. 제어 역행화는 리팩토링의 저자이기도 한 마틴 파울러의 홈페이지에 잘 정의되어 있다.

제어 역행화라는 용어는 직관적이지 못하기 때문에, 또 다른 말로 연관성 삽입(Dependency Injection)이라고도 불려진다. 연관성 삽입 패턴은 컴포넌트의 설정을 그것의 사용에서 분리해야 한다는 원칙(The principle of separating configuration from use)에서 출발한다. 그러한 원칙을 위한 또 다른 사례는 J2EE 패턴 중 서비스 로케이터(Service Locator) 패턴이다.

의존성 삽입 패턴을 이해하기 위해 간단한 예를 하나 살펴보도록 하자. 어느 특정 감독이 만든 영화를 검색해서 그 결과를 전달해주는 컴포넌트를 사용하는 것이다. 코드에서 보여지는 findAll()이라는 메쏘드를 가지는 finder 객체가 필요하단 사실을 알 수 있다.

일반적으로 이럴 때 기능 확장을 위해 MovieFinder와 같은 인터페이스를 작성하게 된다.


public interface MovieFinder {
    List findAll();
}


영화 정보가 콜론으로 구분된 CSV 파일에 기록되어 있다면, MovieFinder 인터페이스를 구현한 ColonDelimitedMovieFinder 클래스가 필요할 것이다. 또한 그 정보는 MovieLister에 생성자를 이용해서 초기화될 것이다.


class MovieLister...
    private MovieFinder finder;
    public MovieLister() {
        finder = new ColonDelimitedMovieFinder("movies1.txt");
    }


전체적인 시스템 구조는 <그림 5>와 같은 형태이다. MovieLister 클래스는 MovieFinder 인터페이스 정보만을 이용해서 기능을 구현할 수 있지만, 해당 기능을 사용하기 위해 MovieFinderImpl 클래스 중에서 ColonDelimitedMovieFinder를 이용한다는 구체적인 설정 정보가 클래스의 코드에 포함되어 있다. 이것은 데이터가 DB나 XML로 변경되어 RDBMovieFinder나 XMLMovieFinder로 교체될 경우 코드를 수정해서 다시 빌드해야 한다는 사실을 의미한다.

<그림 5> 일반적인 제어 흐름을 통한 의존성 표현


사실 MovieLister는 정보가 CSV 파일에 기록되어 있건, DB에 기록되건, XML에 기록되건 영향을 받지 않아야 정상이라고 할 수 있다. 어떻게 하면 이처럼 불필요한 의존 관계를 없앨 수 있는 것일까?

<그림 6> 제어역행화 패턴을 통한 의존성 삽입


그 해답은 설정 정보에 따라 어떤 구현 객체를 사용할 것인지를 결정하는 어셈블러를 이용해서 <그림 6>에서 보여지는 구조로 애플리케이션을 개발하는 것이다.

MovieLister 클래스에서 선언된 MovieFinder 타입의 finder를 초기화하는 방법은 크게 2가지가 있다. 첫 번째는 생성자를 통해 속성을 초기화하는 것이고, 두 번째는 setMovieFinder()와 같은 Setter 메쏘드를 이용하는 것이다. 이러한 설정을 자동화하는 어셈블러를 구현해 놓은 것이 바로 IoC 컨테이너이다.

연관성 삽입은 크게 Constructor Injection, Setter Injection, Interface Injection의 3가지 유형을 가진다. Constructor Injection이 생성자를 이용해서 의존성을 설정해주는 방법이고, Setter Injection이 Setter 메쏘드를 이용해서 의존성을 설정해주는 방법이다. Pico Container는 Constructor Injection을 주로 사용하고, 스프링은 자바 빈 규칙을 이용한 Setter Injection을 주로 사용한다.

스프링의 IoC적인 특징은 AOP를 구현하는 핵심적인 원리가 되기도 한다. AOP는 아직 국내에는 생소한 분야이다. 김대곤님이 작성한 간단한 소개글을 통해 AOP의 개념을 잡아보도록 하자.

자금 이체를 하는 프로그램을 작성한다고 가정해 보자. 출금 계좌와 입금 계좌 그리고 이체 금액을 입력받아 SQL 문장 또는 함수 한 번 돌리는 것으로 모든 프로그래밍이 끝나는가? 그렇지 않다. 해킹을 방지하기 위해 사용자가 적절한 보안 프로그램을 설치했는지 점검하는 코드도 있어야 하고, 사용자가 인증되었는지 점검하는 코드도 써야 하고, 상대방 은행에서 적절하게 처리되었는지도 점점해야 하고, 혹시 사용자가 이체 버튼을 두 번 누른 것은 아닌가 체크해야 하고, 시스템 로그도 남겨야 한다.

즉, 구현하려고 하는 기능 뿐 아니라 보안, 인증, 로그, 성능과 같은 다른 기능들도 녹아 있어야 한다는 뜻이다. 어쩌면 이체를 위한 코드보다 잡다한 다른 측면의 문제들을 다루는 코드가 더 길어질 수 있다. 이런 코드들은 입금이나 출금 같은 다른 곳에서도 공통적으로 사용되는 것이다.

<그림 7> AOP의 개념


구현하려고 하는 비즈니스 기능들을 AOP에서는 Primary(Core) Concern이라는 용어로 표현한다. 보안, 로그, 인증과 같이 시스템 전반적으로 산재된 기능들은 Cross-cutting concern이라고 부른다. AOP는 Cross-cutting concern을 어떻게 다룰 것인가에 대한 새로운 패러다임이라고 할 수 있다.

그럼 AOP가 등장하기 이전에 우리는 어떻게 Cross-cutting Concern을 처리해왔을까? 매우 간단하다. Primary Concern를 구현한 프로그램에 함께 포함시켰다. Primary concern, Cross-cutting concern이 하나의 프로그램 안에 들어가게 되면, 프로그램을 이해하기가 힘들고, Cross-cutting concern 코드가 여기저기에 산재되어 수정하기 힘들게 된다. 당연히 생산성 떨어지고, 품질 떨어지고, 유지보수 비용은 많이 들게 된다.

그럼 AOP는 Cross-cutting concern를 어떻게 처리하는가? AOP에서는 Primary Concern 구현하는 코드 따로, Cross-cutting concern 구현하는 코드도 따로 작성한다. 나중에 2개를 조합한 완벽한 애플리케이션이 만들어지는 것이다.

AOP에서는 Cross-cutting concern 구현한 코드를 Advice라고 하며, Primary concern 구현한 코드를 Code라고 부른다. Code와 Advice를 연결해주는 설정 정보를 Point-cut이라고 하며, 둘을 조합해서 애플리케이션으로 완성하는 과정을 Weaving(조합)이라고 부른다.

기술적 용어로서의 ‘Aspect’는 Advice와 Point-cut을 함께 지칭하는 단어이다. Point-cut은 어떤 Advice를 Code 어느 위치에 둘 것인가 하는 것이다. 스프링의 AOP 패키지는 이러한 AOP 개념을 구현한 것으로, 그 기반에는 설정을 이용으로부터 분리하는 의존성 삽입 패턴이 녹아 있다.

예제 1. 스프링의 WebMVC를 이해하자
이제 스프링을 이해하기 위해 2가지 예제를 살펴볼 것이다. 첫 번째 예제는 스프링 공식 홈페이지에 소개된 것으로 순수하게 스프링이 제공하는 기능만을 이용해서 개발된 예제이다.

국내에는 스프링과 관련한 자료가 전혀 없는 관계로 초보자들을 위해 이 예제를 편역하고 몇 가지 설명을 추가해 총 4부 8개의 강좌로 재구성해서 VSSH 포럼에 등록해 두었다. 자세한 설명을 원하는 독자는 관련 자료를 참고하기 바란다.

여기서 소개하는 예제는 앞에서 소개한 강좌 중 2부까지 구현된 샘플이다. 예제를 실행해보기 위해서는 톰캣과 Ant, JDK 등이 설치되어 있어야 한다. 예제(springapp.zip)를 다운로드한 다음, 작업 디렉토리에서 압축을 풀면 build.properties 파일이 보일 것이다.

해당 파일을 열어 배포될 경로와 톰캣 홈, 그리고 톰캣 관리자의 URL/아이디/패스워드 정보를 자신의 환경에 맞게 변경한다. 그런 다음 build와 deploy 타겟을 ant를 이용해서 순서대로 실행한다. 웹 브라우저를 이용해서 http://localhost:8080/springapp로 접속하면 아래와 같은 결과 화면을 볼 수 있을 것이다.

<그림 8> 스프링 WebMVC 예제화면


우선 예제의 web.xml 설정부터 살펴보도록 하자. DispatcherServlet이 springapp란 이름으로 등록되어 있고, htm으로 끝나는 모든 URL 패턴이 해당 서블릿으로 매핑되어 있다. DispatcherServlet은 스트럿츠의 ActionServlet의 기능과 유사한 일종의 FrontController 서블릿이다.


<web-app>
  <servlet>
    <servlet-name>springapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>
</web-app>


그렇다면 DispatcherServlet이 처리하는 제어 행위를 위한 struts-config.xml과 같은 설정 파일이 필요할 것이다. 스프링 MVC 패키지에서는 그 파일을 해당 서블릿 이름에 "-servlet.xml"을 붙여 작성하도록 권장하고 있다. 앞의 경우 서블릿의 이름을 springapp로 주었기 때문에 springapp-servlet.xml이 설정 파일이 되는 것이다.

설정 파일을 보면, <beans>라는 루트 엘리먼트 밑에 <bean>이라는 설정이 반복되어 적용되고 있다. 이것이 스프링에서 BeanFactory를 이용해서 제공하고 있는 Setter Injection을 통해 설정과 그 사용을 분리하는 방법이다.


<beans>
    <bean id="springappController" class="web.SpringappController">
        <property name="productManager">
            <ref bean="prodMan"/>
        </property>
    </bean>
    <bean id="prodMan" class="bus.ProductManager">
        <property name="products">
            <list>
                <ref bean="product1"/>
                <ref bean="product2"/>
                <ref bean="product3"/>
            </list>
        </property>
    </bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/hello.htm">springappController</prop>
            </props>
        </property>
    </bean>
    ... (생략) ...
</beans>


전체 애플리케이션의 동작 과정을 한번 살펴보자.

<그림 9> 스프링 WebMVC 예제동작 원리


웰컴 파일인 index.jsp에는 hello.htm에 대한 링크가 걸려있다. hello.htm에 대한 요청은 web.xml의 설정에 의해 springapp로 이름지어진 스프링의 web 패키지의 DispatcherServlet으로 전달된다. DispatcherServlet은 스프링의 core 패키지에 있는 BeanFactory를 이용해 IoC 컨테이너의 기본 원리에 따라 동작된다. 설정 파일인 springapp-servlet.xml을 통해 모든 의존성이 결정되어 제어가 반전되는 현상을 볼 수 있다.

<그림 10> VSSH 고객등록 예제화면


우선 <prop key="/hello.htm">springappController</prop>에 의해 hello.htm 요청을 SpringappController 클래스가 처리하게 되는데 그 사실을 DispatcherServlet은 전혀 모른다. SpringappController는 요청을 처리하는 과정에서 ProductManager의 구현을 필요로 하는데, 그게 어떤 클래스의 인스턴스인지를 본인은 모른다. 설정에 의해 자동적으로 bus.ProductManager가 결정되고, 해당 인스턴스가 거꾸로 SpringappController에 찾아와 연결된다. 이러한 제어의 반전이 스프링을 IoC(Inversion of Control) 컨테이너라고 부르게 하는 이유다.

다음 코드는 스트럿츠의 액션과 유사한 역할을 수행하는 Controller 구현 샘플이다. ProductManager를 사용하고 있는데 신기하게도 setProductManager() 메쏘드만 있을 뿐, 인스턴스를 초기화하는 호출이 이루어지지 않는다. 스프링이 빈 설정을 참고해서 자동화하기 때문이다. handleRequest()의 결과로 ModelAndView가 리턴되어 넘어가는데, 이것은 스트럿츠의 ActionForward와 컨텍스트 정보를 합친 것으로 이해하면 되겠다.


public class SpringappController implements Controller {
protected final Log logger = LogFactory.getLog(getClass());
    private ProductManager prodMan;
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String now = (new java.util.Date()).toString();
        logger.info("returning hello view with " + now);
        Map myModel = new HashMap();
        myModel.put("now", now);
        myModel.put("products", getProductManager().getProducts());
        return new ModelAndView("hello", "model", myModel);
    }
    public void setProductManager(ProductManager pm) {
        prodMan = pm;
    }
    public ProductManager getProductManager() {
        return prodMan;
    }
}


예제 2. 스프링과 스트럿츠, 하이버네이트를 통합한 예제
그럼 스프링을 기존의 스트럿츠 환경과 어떻게 연동할 수 있을까? 대표적인 ORM인 하이버네이트와는 어떻게 연동할 수 있을까? 이를 소개하기 위해 간단한 회원 관리 샘플을 개발했다. 이 예제는 벨로시티와 스트럿츠, 스프링, 하이버네이트를 통합하고자 하는 VSSH의 최종적인 모습을 보여주며, DBMS는 HSQLDB를 사용했다.

예제 파일(vssh.zip)을 다운받아 자신의 작업 디렉토리에 압축을 풀고, was.properties 파일을 열어 자신의 운영 환경에 맞게 경로를 수정한다. 그런 다음, ant를 이용해서 makeDist, war-deploy 태스크를 순서대로 실행하면 빌드 및 배포 과정이 끝난다. 웹 브라우저를 이용해서 http://localhost:8080/vssh-b01에 접속하면 아래와 같은 결과 화면을 볼 수 있을 것이다.

이 예제는 다음 강좌에서 다시 자세히 설명할 예정인데, 미리 상세한 내용을 알고 싶은 분은 LoveLazur의 홈페이지를 참고하기 바란다. 이번 예제부터는 복잡하기 때문에 이클립스와 같은 오픈소스 IDE를 이용하는 것이 좋다.

스트럿츠와 스프링의 연동 방법은 각각을 이해하고 있다면 아주 간단히 처리된다. WEB-INF/lib 디렉토리에 각각의 라이브러리 JAR 파일을 추가한 다음, 스트럿츠 설정 파일인 struts-config.xml에 플러그인 설정만 하나 추가하면 되는 것이다. 플러그인으로 설정한 ContextLoader는 전달된 설정 파일을 이용해서 전체 애플리케이션 구성에 사용될 ApplicationContext를 구성하는 역할을 수행한다.


<struts-config>
<form-beans> ... </form-beans>
<action-mappings> ... </action-mappings>
<message-resources parameter="messages"/>
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
     <set-property property="contextConfigLocation"
            value="/WEB-INF/applicationContext.xml, /WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>


applicationContext.xml에는 애플리케이션 로직과 관련된 객체들을 앞서 설명한 빈 설정 방식으로 연동해서, 제어 역행화가 이루어지도록 하면 된다.


<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url"><value>jdbc:hsqldb:data/vsshdb</value></property>
<property name="username"><value>sa</value></property>
<property name="password"><value></value></property>
</bean>
  <!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list><value>model/Customer.hbm.xml</value></list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
<!-- Transaction manager , can replace class Attribute ex.Transaction-->
<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="customerDAO" class="dao.CustomerDAOImpl">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>    
</beans>


주의할 점은 모든 클래스에서 사용되는 객체 참조에서 항상 자바 빈 규칙에 따르는 setter()를 만들고, 직접 인스턴스를 초기화하지 않고 객체들의 의존성을 설정 파일에 적어줌으로써 스프링이 자동으로 의존성 관계를 삽입하도록 해야 한다는 것이다.

다음의 CustomerAction을 살펴보면 ICustomerBizManager 타입의 cmgr이라는 인스턴스를 갖고 있는데, setCustomermanager()란 메쏘드만 있을 뿐 어디에도 초기화하는 루틴이 포함되어 있지 않다. 그런데도 cmgr.createCustomer()를 사용하고 있는 것을 볼 수 있다.


public class CustomerAction extends BaseAction {
private ICustomerBizManager cmgr = null;
public void setCustomerManager(ICustomerBizManager cmgr) { //def.
this.cmgr = cmgr;
}
public ActionForward list(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
request.setAttribute("custlist", cmgr.getCustomers());
return mapping.findForward("list");
}
public ActionForward create(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
DynaActionForm custForm = (DynaActionForm) form;
cmgr.createCustomer((Customer) custForm.get("cust"));
ActionMessages messages = new ActionMessages();
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                "cust.saved"));
return list(mapping, form, request, response);
}
}


이것은 action-servlet.xml과 applicationContext.xml에서 다음과 같은 설정이 되어 있으므로 그 정보에 따라 적절한 customerManager가 결정되기 때문에 가능해진다.


<beans>
<bean name="/cust" class="control.CustomerAction" singleton="false">
<property name="customerManager">
<ref bean="customerBizManager"/>
</property>
</bean>
<bean id="customerBizManager"
        class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="transactionManager"/></property>
<property name="target"><ref local="customerBizManagerTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="create*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>    
</beans>


대안 기술에 대해 관심을 갖자
지금까지 스프링 프레임워크의 필요성을 설명하고, 기본 개념이라 할 수 있는 연관성 삽입 패턴과 AOP에 대해 간단히 살펴보았다. 또한 스프링의 특징과 스프링을 활용한 예제를 소개했다. 스프링이 아직 국내에 소개된 적이 없다보니 외국의 문서와 예제, 서적들을 뒤적거리면서 관련 자료를 정리하는데 한달이 넘는 시간이 소요되었다.

하지만 아쉽게도 그동안 정리한 내용을 모두 소개하기에는 할당된 지면이 너무 부족해 꼭 필요한 부분만을 알려주는데 그친 듯하다. 설명이 미흡한 부분은 필자들의 VSSH 포럼을 활용하고, 궁금한 점이 있으면 언제든 질문해 주기 바란다.

솔직히 말해 이 글을 쓰고 있는 필자도 여러분보다 몇 달 먼저 스프링을 공부한 정도의 수준밖에 되지 않는다. 미국, 일본, 중국, 독일 어디서나 스프링 관련 자료를 쉽게 구할 수 있었지만, 안타깝게도 국내에는 전혀 자료가 없었다.

해외에서는 크게 주목을 받고 있는 스프링과 IoC 컨테이너에 대한 내용들이 왜 국내에서는 전혀 다루어지지 않고 있는지를 곰곰이 생각해본다. 우리네 개발자들은 촉박한 개발 일정에 쫓겨 신기술이나 대안 기술들을 공부할 만큼 다들 여유가 없는 것일까? 아니면 그러한 기술을 이미 익히고 있는 개발자들이 자신의 머리 속에 그 지식을 꼭꼭 숨겨두고 공개하지 않는 것일까?

지금 자바 커뮤니티는 주류 기술의 버전 향상과 신기술의 출현 그리고 오픈소스 기반의 다양한 대안 기술의 등장으로 어느 때보다 급격한 기술 변화를 눈앞에 두고 있다. 아직 그러한 변화를 느끼지 못하고 있는 개발자라면 지금이 바로 그러한 변화에 대한 대비를 시작해야 할 때임을 깨달아야 한다. 또한, 그 과정에서 얻어진 노하우를 공유함으로써 더 큰 이익이 자신에게 돌아온다는 사실도 잊지 말기 바란다. @

 관련파일 링크

출처 : 마이크로소프트(마소 2004.10월)

 

지난 글에서 소개한바와 같이 마소에서 연재 되었던 오픈소스를 이용한 시스템통합 두번째 컬럼인 스트럿츠와 벨로시티의 통합 이라는 제목이라는 글입니다.

 

1회, 오픈소스를 이용한 시스템 통합, 그 즐거운 도전의 시작
2회, 첫번째 도전! 스트럿츠와 벨로시티의 통합

3회, 약한 결합도 아키텍처를 위한 대안 기술, 스프링
4회, OR 맵핑 도구의 선두 주자, 하이버네이트

지난 글 에서는 오픈소스가 가지고 있는 무한한 가능성이 어떻게 실무와 연동될 수 있는지를 살펴보고, 관련 사례와 VSSH 프레임워크의 개발 방향에 대해 간단히 소개했다. 이번 호에서는 VSSH 프레임워크를 구성하는 기술 중에서 벨로시티와 스트럿츠를 통합하는 방법에 대해 이야기를 풀어나가고자 한다.

벨로시티와 스트럿츠 연동을 위한 예제
<그림 1>과 같은 단순한 구조를 가진 로그인 예제를 개발해 나가면서 벨로시티와 스트럿츠의 연동 방법을 살펴보도록 하자(이 예제는 참고자료 2의 17장에 소개된 예제를 벨로시티와 스트럿츠의 연동 과정을 설명하기 위한 목적으로 수정한 것이다).

<그림 1> 로그인 예제의 구성

로그인 예제는 로그인 기능과 로그아웃 기능만을 가지는 아주 간단한 웹 애플리케이션이다. 처음 사이트를 방문하게 되면 환영 메시지와 함께 로그인 링크가 나타난다. 로그인 링크를 클릭하면 사용자 아이디와 비밀번호를 입력할 수 있는 로그인 화면이 뜬다. 사용자가 입력한 아이디와 비밀번호가 등록된 것이라면 다시 초기 화면으로 이동해서 로그인 정보를 보여주고, 그렇지 않다면 에러 메시지를 출력하고 다시 로그인하도록 한다.

로그인 상태에서는 사용자 아이디가 출력되며, 로그아웃 버튼을 누르면 로그아웃됨과 동시에 초기화면으로 이동하는 것이다. 자기가 개발해야 하는 도메인 영역에 대해 많은 경험이 없다면, 분석된 유즈케이스 시나리오를 토대로 위와 같이 사용자 인터페이스를 그려보고, 각 사용자 인터페이스간의 흐름을 파악한 다음 각각의 흐름에 이름을 붙여두는 것이 좋다. 이 방법은 해결해야 할 문제 자체를 더 잘 이해할 수 있도록 도와줄 뿐 아니라, 스트럿츠의 액션 설계에도 도움을 준다.

로그인 예제에서 사용되는 정보는 아이디와 비밀번호만으로 유지한다. 이 정보는 user.properties라는 프로퍼티 파일에 기록되며, 아이디는 대소문자 구별을 하지 않기 위해 대문자로 미리 변환시킨 형태로 기록해 두도록 하겠다(. 다음 연재에서는 이 정보를 데이터베이스로 옮겨 저장하고, CMP 방식의 엔티티 빈을 이용해서 Spring 프레임워크에서 처리되도록 확장할 것이다).

형식 : {USERID}={password}
예제 : VSSH=good

예제를 위한 환경 구성
로그인 예제를 개발하고 운영하기 위해 다음과 같은 환경을 구성했다. 지면 관계상 각 도구의 설치 및 사용 방법에 대해서는 필자들이 운영하는 VSSH 포럼(참고자료 1)에 따로 기록해 두었으므로 환경 구성이나 도구 사용법에 대해 알고 싶은 독자들은 홈페이지를 참고하기 바란다. 애플리케이션 서버의 경우 앞으로의 연재를 고려해 JBoss로 선택했을 뿐, 이번 예제에서는 톰캣만으로 충분하다. 개발 도구의 경우도 생산성 향상 및 디자이너와의 협업을 쉽게 할 수 있는 벨로시티의 장점을 보여주기 위해 드림위버용 벨로시티 플러그인과 이클립스용 벨로시티 플러그인을 사용했다. 자신이 주로 사용하는 IDE가 있다면 굳이 이러한 환경을 똑같이 사용할 필요는 없다.

◆ 자바 : JDK 1.4.2
◆ 애플리케이션 서버 : JBoss 3.0.8(with 톰캣 4.1.24)
◆ 개발 도구 : 드림위버 MX 2004(Velocity Suite v1.2.0), 이클립스 3.0(JBoss IDE 1.3.0, Veloeclipse)
◆ 라이브러리 : 스트럿츠 1.1, 벨로시티 1.4, 벨로시티 툴 1.1

벨로시티를 이용한 Hello, World!
다음은 벨로시티를 이용한 Hello World! 예제이다. 이 예제는 디자인에 해당하는 템플릿 파일과 처리 로직을 담고 있는 자바 코드로 구성된다.

#set( $name = "Seal" )
Hello Velocity World, $name is Here

템플릿 파일을 들여다보면, 일반 텍스트에 #이나, $ 기호가 붙은 이상한 코드들이 포함되어 있다. 이것이 벨로시티 템플릿에서 사용되는 VTL(Velocity Template Language)이다. VTL은 벨로시티에서 템플릿 개발에 사용되는 언어이로 레퍼런스(Reference), 디렉티브(Directive), 그리고 주석(Comment)으로 구성된다.

<표 1> 벨로시티 템플릿 언어 정리

VTL은 <표 1>에서 정리한 것처럼 너무나 단순하기 때문에 흔히 성냥갑 표지에 다 적을 수 있을 만큼 작은 API라고 불려진다. 이는 뷰 작업을 개발자와 디자이너가 함께 진행해 나간다는 점을 감안하면 매우 바람직한 일이라고 볼 수 있다. VTL을 통해 작성된 벨로시티 템플릿(vm 파일)은 다음과 같은 패턴을 통해 처리 된다.

Velocity.init("velocity.properties");  ...................................................... (1)
VelocityContext context = new VelocityContext();.................................... (2)
Template template = null;
template = Velocity.getTemplate("HelloWorld.vm"); ................................. (3)
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(System.out));
if ( template != null)
template.merge(context, writer);  ................................................... (4)
writer.flush();
writer.close();

앞의 소스코드는 벨로시티의 처리 과정을 보여줄 수 있는 핵심 코드만을 추출한 것이다. 벨로시티에서는 템플릿을 처리하기 위해 다음과 같은 4단계의 동작을 수행한다.

[1] 벨로시티 템플릿 엔진의 초기화(초기화 설정은 velocity.properties 파일을 사용)
[2] 자바 클래스와 템플릿간의 정보 전달에 사용할 컨텍스트 객체 생성, 컨텍스트 객체는 해시 테이블을 상속받으므로 (키, 값)의 순서쌍 형태로 값을 저장
     예) context.put("members", memberDAO.getMembers());
이렇게 해서 컨텍스트에 저장된 값들은 템플릿에서 레퍼런스를 이용해서 참조. 컨텍스트에 담긴 값이 컬렉션일 경우 #foreach 디렉티브를 통해 하나씩 참조 가능
[1] 로직과 연결되어 사용될 템플릿 파일을 선택
[2] 템플릿과 컨텍스트를 렌더링해서 그 결과를 출력

템플릿 엔진이 동작하는 방식을 정리해 보면, 개발자는 사용자에게 보여 질 디자인을 위해 VTL을 이용한 템플릿 파일을 작성하고, 그 처리를 위한 코드를 개발한다. 자바 코드와 템플릿 간의 필요한 정보 전달은 컨텍스트 객체를 통해 이루어진다. 이러한 2개의 파일을 입력받아 벨로시티는 템플릿을 토대로 렌더링한 결과 페이지를 출력하게 되는 것이다. 템플릿에 어떤 내용이 담겨 있느냐에 따라 결과 페이지는 일반 텍스트가 될 수도 있고, HTML이나 SQL, PostScript, XML 등도 될 수 있다.

<그림 2> 템플릿 엔진의 동작 원리

앞의 예제는 콘솔에 텍스트 결과를 출력하도록 작성되어 있으므로 다음과 같은 실행 결과가 나타나게 될 것이다.

Hello Velocity World, Seal is Here

VelocitySuite를 이용한 로그인 예제 템플릿 개발
스트럿츠와 벨로시티를 연동한 웹 개발에서 사용되는 벨로시티 템플릿은 사실 앞에서 소개한 VTL 문법을 이용해서 스트럿츠 태그 라이브러리를 감싼 것에 지나지 않는다. 그런 이유로 스트럿츠 태그 라이브러리로 작성된 JSP 파일과 스트럿츠 기반의 벨로시티 템플릿은 1:1 대응을 통해 상호 변환이 가능한 경우가 많다. 그럼에도 불구하고 벨로시티를 사용하는 이유는 벨로시티가 지니고 있는 다음과 같은 몇 가지 장점 때문이라고 할 수 있다.

◆ 벨로시티 템플릿은 같은 기능의 JSP에 비해 코드 양이 적고 렌더링 속도도 빠르다.
◆ 템플릿 방식이기 때문에 다양한 출력 양식의 지원이 쉽다.
◆ HTML 편집기를 통해 쉽게 볼 수 있기 때문에 디자이너가 작업하기 편하다.

이러한 벨로시티의 장점을 십분 활용하기 위해 로그인 예제의 템플릿 개발에 드림위버와 VelocitySuite라는 플러그인을 사용해 보도록 하자. VelocitySuite는 매크로미디어에서 제공하는데, 벨로시티 홈페이지에 다운로드할 수 있는 링크가 제공된다. 현재 VelocitySuite는 1.2 버전으로 드림위버 MX 2004 버전에서 제대로 동작한다. 링크를 따라가면 VelocitySuite16.mxp라는 파일이 다운로드되는데, 해당 파일을 더블클릭하면 드림위버의 확장기능 관리자(Extension Manager)가 실행되어 쉽게 설치할 수 있다. 설치가 되고 나면, 벨로시티 템플릿이 생성될 기본 파일 양식에 추가된다. 벨로시티 템플릿을 생성시키면 다음과 같은 툴바가 상단에 나타나는데, 이것이 바로 VTL의 각 요소를 빠르게 입력할 수 있도록 도와주는 툴이다.

<그림 3> 드림위버용 VelocitySuite의 툴바

로그인 예제에서 사용되는 템플릿 파일은 2개뿐이다.

◆ Welcome.vm : 환영 메시지를 담은 초기 화면으로, 로그온/로그오프에 대한 링크 제공
◆ Logon.vm : 아이디와 비밀번호를 입력받는 화면으로, 에러 페이지 기능을 포함

<화면 1>은 드림위버를 이용해서 Welcome.vm 파일을 작성하는 과정을 캡처한 것이다. 보는 바와 같이 비주얼한 환경에서 디자이너에게 친숙한 방식으로 쉽게 템플릿 제작이 가능함을 확인할 수 있다.

<화면 1> 드림위버를 이용한 welcome.vm 파일 제작 과정

벨로시티를 이용하면 VTL 요소와 HTML 요소의 구분이 명확하기 때문에 디자이너와 프로그래머가 실수로 서로의 작업을 해치게 되는 경우를 피할 수 있다. 다음의 코드는 Welcome.vm 파일의 전체 소스코드이다. #if 디렉티브를 이용해서 사용자 정보 여부에 따라 서로 다른 화면이 출력됨을 쉽게 이해할 수 있을 것이다.

<HTML>
<HEAD>
    <TITLE>로그인 예제 : Velocity+Struts 버전 (Welcome Page)</TITLE>
    <META http-equiv="Content-Type" content="text/html;charset=euc-kr">
    <BASE href="$link.baseRef">
</HEAD>
<BODY>
    <p><IMG src="vssh.gif"></p>
    <P>
    #if( $user )
        <font color="blue">$user.username</font>님이 로그인하셨습니다.
    #else
        VSSH 프로젝트에 오신 것을 환영합니다.
    #end
    </P>  
    $!errors.getMsgs()
    <UL>
    #if( $user )
        <LI><A href="$link.setForward('logoff')">로그아웃</A></LI>
    #else
        <LI><A href="$link.setForward('logon')">로그인</A></LI>
    #end
    </UL>
</BODY>
</HTML>

다음의 소스코드는 Logon.vm 파일의 주요 코드를 나타낸 것이다. 아이디와 비밀번호를 입력할 수 있도록 폼이 구성되어 있고, 에러가 있을 경우 그 메시지를 출력하도록 작성되어 있다.

<BASE href="$link.baseRef">
$!errors.getMsgs()
<FORM method="POST" action="$link.setAction('/LogonSubmit')">
<INPUT type="text" name="username" value="$!logonForm.username">
<INPUT type="password" name="password" value="$!logonForm.password">
<INPUT type="submit"><INPUT type="reset">  ## 일부 코드는 생략됨

템플릿에 사용된 VTL 코드는 스트럿츠 태그 라이브러리를 활용한 JSP로도 표현될 수 있다. <표 2>에서 확인할 수 있듯이 벨로시티 템플릿과 태그 라이브러리가 적용된 JSP의 기능성은 똑같다. 하지만 JSP에 비해 상대적으로 템플릿 코드가 더욱 간결하고, 이해하기 쉽게 작성되어 있음을 확인할 수 있을 것이다. 이 점이 벨로시티를 사용하는 또 다른 이유가 된다.

<표 2> 벨로시티와 JSP 비교

벨로시티의 유용한 도구 모음, VelocityTools!
VelocityTools는 벨로시티를 이용한 애플리케이션 개발에 사용되는 유용한 도구들의 모음으로, 현재 1.1 버전까지 나와 있는 상태이다. VelocityTools는 크게 GenericToos, VelocityView, VelocityStruts로 이루어져 있다.

◆ GenericTools : 어떤 형식의 자바 객체에서도 사용될 수 있는 날짜 조작, 숫자 포매팅과 같은 일반적인 도구들로 구성된 유틸리티. DateTool, MathTool, NumberTool, IteratorTool, RenderTool로 구성됨.
◆ VelocityView : 벨로시티를 이용한 빠른 웹 개발에 사용되는 도구 모음. 벨로시티 템플릿의 렌더링을 담당할 VelocityViewServlet이 포함되어 있으며, toobox 설정을 통해 여러 도구들을 템플릿 내에서 사용할 수 있도록 함. VelocityView를 이용하면 세션, 컨텍스트, HTTP 요청 등에 담긴 정보를 템플릿 내에서 이용할 수 있고, 각종 로깅 API를 지원함. AbstractSearchTool, CookieTool, ImportTool, LinkTool, ParameterTool, ViewRenderTool로 구성됨.
◆ VelocityStruts : 벨로시티와 스트럿츠의 프레임워크 통합을 위해 제공되는 도구 모음. 스트럿츠에서 사용되는 JSP와 대응되는 기능을 가진 템플릿을 만들 수 있는 각종 도구들이 포함됨. ActionMessageTool, ErrosTool, FormTool, MessageTool, StrutsLinkTool, SecureLinkTool, TilesTool, ValidatorTool로 구성됨.

<그림 4>는 로그인 예제의 전체 디렉토리 구조도를 표시한 것이다. 스트럿츠와 벨로시티를 통합해 사용하기 위해서는 애플리케이션의 WEB-INF 밑에 있는 lib 디렉토리에 스트럿츠 관련 라이브러리, 벨로시티 관련 라이브러리와 함께 VelocityTools 라이브러리를 함께 넣어둬야 한다.

<그림 4> 로그인 예제의 전체 디렉토리 구조도

필요한 설정 파일
벨로시티와 스트럿츠를 통합해서 사용한 웹 애플리케이션은 3개의 설정 파일을 필요로 한다. WEB-INF 디렉토리 밑에 위치한 web.xml, struts-config.xml, toolbox.xml이 바로 그것이다.

◆ web.xml : J2EE 기반 표준 웹 애플리케이션 설정 파일
◆ struts-config.xml : 스트럿츠 설정 파일
◆ toolbox.xml : 벨로시티 툴 설정 파일

앞에서 살펴본 Hello, World 예제에서와 같이 벨로시티를 사용하기 위해서는 템플릿 파일뿐 아니라 그 처리 과정을 담당할 자바 코드가 필요하다. VelocityTools에는 이러한 코드를 자동화 해놓은 VelocityViewServlet이 포함되어 있는데, 이를 통해 개발자는 자질구레한 렌더링 과정에 대해서는 신경을 쓰지 않을 수 있다. VelocityViewServlet를 이용하려면 이 클래스가 포함된 velocity-tools-view-1.1.jar 파일을 lib 디렉토리에 위치시키고, web.xml 파일에서 서블릿 설정을 추가해 주어야 한다.

<web-app>
  <!-- 벨로시티를 위한 벨로시티 뷰 서블릿 설정 -->
  <servlet>
    <servlet-name>velocity</servlet-name>
    <servlet-class>org.apache.velocity.
      tools.view.servlet.VelocityViewServlet</servlet-class>
    <init-param>
      <param-name>toolbox</param-name>
      <param-value>/WEB-INF/toolbox.xml</param-value>
   </init-param>
   <load-on-startup>10</load-on-startup>
  </servlet>

  <!-- *.vm 파일을 벨로시티 뷰 서블릿과 연결  -->
  <servlet-mapping>
    <servlet-name>velocity</servlet-name>
    <url-pattern>*.vm</url-pattern>
  </servlet-mapping>
</web-app>

이 설정을 통해 vm 확장자가 붙은 모든 파일은 velocity로 이름지어진 VelocityViewServlet을 통해 처리됨을 확인할 수 있다. 서블릿 설정의 초기 파라미터로 toolbox 파라미터가 전달되고 있는데, 이는 툴(tool)이라고 부르는 일련의 미리 정의된 객체들을 통해 스트럿츠의 커스텀 태그에서 지원되는 각종 기능을 이용해서 템플릿을 작성할 수 있도록 도와주는 역할을 수행한다.

<toolbox>
  <tool>
     <key>toolLoader</key>
     <class>org.apache.velocity.tools.tools.ToolLoader</class>
  </tool>
  <tool>
     <key>link</key>
     <class>org.apache.velocity.tools.struts.LinkTool</class>
  </tool>
  <tool>
     <key>msg</key>
     <class>org.apache.velocity.tools.struts.MessageTool</class>
  </tool>
  <tool>
     <key>errors</key>
     <class>org.apache.velocity.tools.struts.ErrorsTool</class>
  </tool>
  <tool>
     <key>form</key>
     <class>org.apache.velocity.tools.struts.FormTool</class>
  </tool>
</toolbox>

이 코드는 로그인 예제에서 사용된 툴 박스 설정의 샘플이다. 각 툴을 로딩하는 ToolLoader가 포함되어 있고, LinkTool, MessageTool, ErrorsTool, FormTool이 사용되고 있음을 확인할 수 있다. StrutsTools가 버전이 바뀌면서, 몇 몇 툴들의 패키지 구조가 변경되었다. 만일 자신의 StrutsTools를 버전업하려면, 벨로시티 홈페이지에서 각 툴의 레퍼런스 문서들을 참고해서 툴 박스 설정을 확인해보는 것이 좋다. 홈페이지에는 그 밖에도 툴의 기능 설명과 VTL에서 참조할 레퍼런스 이름, 지원되는 속성이나 메쏘드 목록 등도 확인할 수 있다.

새로운 툴을 추가적으로 사용하고자 한다면 툴박스 설정을 추가하는 것만으로 간단히 해결된다. Tiles를 연동해서 개발하고 싶다면 다음과 같은 설정 정보가 toolbox.xml에 추가될 것이다.

<tool>
<key>tiles</key>
<scope>request</scope>
<class>org.apache.velocity.tools.struts.TilesTool</class>
</tool>

마지막으로 스트럿츠 설정 파일인 struts-config.xml에 대해 살펴보도록 하자. 로그인 정보 전달을 위한 ViewHelper에 해당하는 폼 빈(LoginForm)에 대한 설정이 하나 추가되어 있고, Welcome, Logon, LogonSubmit, Logoff의 4가지 액션 맵핑이 정의되어 있음을 확인할 수 있다.

<struts-config>
    <!-- 폼 빈의 정의 -->
    <form-beans>
        <form-bean name="logonForm" type="app.LogonForm"/>
    </form-beans>

    <!-- 글로벌 포워드 정보 정의 -->
    <global-forwards>
        <forward name="logoff"  path="/Logoff.do"/>
        <forward name="logon"  path="/Logon.do"/>
        <forward name="welcome"  path="/Welcome.do"/>
    </global-forwards>

    <!-- 스트럿츠 액션 맵핑 -->
    <action-mappings>
        <action path="/Welcome"
            type="org.apache.struts.actions.ForwardAction"
            parameter="/pages/Welcome.vm"/>
        <action path="/Logon"
            type="org.apache.struts.actions.ForwardAction"
            parameter="/pages/Logon.vm"/>
        <action path="/LogonSubmit"
            type="app.LogonAction" name="logonForm"    
            scope="request" validate="true" input="/pages/Logon.vm">
            <forward name="success" path="/pages/Welcome.vm"/>
        </action>
        <action  path="/Logoff" type="app.LogoffAction">
            <forward name="success" path="/pages/Welcome.vm"/>
        </action>
    </action-mappings>
</struts-config>

여기서 주목해야 할 점은 포워드할 대상이 벨로시티 템플릿 파일인 VM 파일로 설정되어 있다는 것이다. 이와 같이 JSP를 VelocityTools에서 지원되는 각종 툴을 이용해서 템플릿으로 변환하고, 필요한 라이브러리를 설치/설정한 다음 JSP로 향한 포워드 정보를 VM 파일로 변경하는 것만으로 간단히 스트럿츠 애플리케이션을 벨로시티와 통합할 수 있다.

로그인 예제의 구현 클래스와 수행 과정
로그인 예제는 단순하면서도 전형적인 스트럿츠 애플리케이션으로 모두 6개의 클래스로 구성되어 있다. 로그인 및 로그오프 동작을 수행할 액션 클래스(LoginAction, LogoffAction)와 user.properties 파일에 기록된 사용자 정보를 처리하는 DAO인 UserDirectory 클래스 및 예외 클래스(UserDirectoryException), 전체적인 환경 정보를 유지하기 위한 Constants 클래스, 마지막으로 계층간의 정보 전달에 사용될 ViewHelper이자 값 객체인 LoginForm 클래스가 그것이다.

<그림 5> 로그인 예제의 클래스 다이어그램

스트럿츠를 알고 있는 개발자라면 누구나 개발할 수 있는 쉬운 예제이기 때문에 구현 로직에 대한 자세한 내용은 생략한다. 전체 소스코드를 다운받고자 한다거나 로그인 구현에 대한 의문점이 있는 경우에는 VSSH 포럼을 이용해주기 바란다.

마지막으로 로그인 예제의 수행 과정을 정리해 보면, <그림 6>에서 표시된 것과 같이 모두 6단계의 과정을 통해 사용자 요청을 처리하게 된다. 스트럿츠가 벨로시티와 통합하게 되면서 달라지는 부분은 5, 6단계에 해당하는 내용이다. 요청에 대한 처리가 끝나면 그 결과를 포워드하게 되는데 struts-config.xml에는 포워드 대상이 벨로시티 템플릿인 VM 파일로 향하도록 설정되어 있다. *.vm이란 확장자를 만나게 되면 web.xml의 서블릿 설정에 의해 VelocityTools에서 제공되는 VelocityViewServlet으로 제어가 이동하게 된다.

VelocityViewServlet은 toolbox.xml에 정의된 각종 툴들을 로드해서 개발자가 정의한 템플릿에 따라 벨로시티 템플릿 엔진에게 렌더링을 요청하게 되고, 그 결과로 생성된 HTML 페이지가 사용자에게 전달됨으로써 사용자는 요청 결과를 보게 되는 것이다.

<그림 6> 로그인 예제의 전체 수행 과정 정리도

VSSH 포럼을 활용하자
지금까지 벨로시티, 스트럿츠, Spring, Hibernate를 통합한 오픈소스 프레임워크인 VSSH를 개발하기 위한 첫 단계로, 벨로시티와 스트럿츠를 통합하는 방법에 대해 살펴보았다. 자카르타 프로젝트에서 제공되는 VelocityTools의 기능을 활용해서 손쉽게 벨로시티와 스트럿츠가 통합되며, 태그 라이브러리가 적용된 JSP 파일의 대안으로 벨로시티가 사용될 수 있음을 살펴보았다.

한 가지 아쉬운 점은 한정된 지면으로 인해 이클립스 3.0을 활용한 오픈소스 기반의 개발 환경 구축에 대한 내용을 소개하지 못한 것이다. 필자의 경우 JBoss를 애플리케이션 서버로 사용하기 위해 Lomboz와 EclipseIDE를 설치하고, 벨로시티 지원을 위해 Veloeclipse를 설치해서 테스트해 보았는데 기대했던 것보다 훨씬 더 훌륭한 개발 환경을 구축할 수 있었다. 그에 관한 내용은 포럼을 통해 정리해 놓을 생각이므로 관심 있는 독자들은 참고하길 바란다.

최근 VSSH 프로젝트가 연재되고 나서부터 포럼의 사용자가 많이 늘어났다. 많은 개발자들이 오픈소스의 가능성을 인정하고, 실무에서 오픈소스 기술들을 자연스럽게 활용할 수 있게 되기 위해서는 우리 모두가 좀 더 많은 경험들을 공유하고 서로가 지닌 노하우를 털어놓을 필요가 있다. 이 연재를 통해 더 많은 개발자들이 오픈소스 기술의 활용에 관심을 가져주기를, 그리고 그 과정을 통해 각자의 기술력을 한 단계 업그레이드 할 수 있기를 진심으로 바란다. @



뷰계층 개발 기술 변화의 흐름

현재 자바 기술은 매일 같이 눈부신 속도로 발전해 가고 있다. 그 중에서도 특히 많은 변화를 겪고 있는 것은 MVC 패턴에 따른 3개의 계층 구조 중에서 사용자 인터페이스를 구성하는 뷰 계층에 해당하는 기술들이다.

모델 2 방식의 JSP 개발이 일반화된 지금, 가장 많이 사용되는 기법은 썬마이크로시스템즈의 설계 가이드라인에 포함된 애완동물 쇼핑몰(PetStore) 예제에서 볼 수 있는 View Helper 패턴과 Composite View 패턴이 결합된 형태의 J2EE 패턴 적용과, 다양한 태그 라이브러리의 활용일 것이다. 하지만 성능, 개발 생산성, 유지보수성, 사용성 등의 향상을 이유로 최근 뷰 계층 기술이 다시 한번 변화할 조짐을 보이고 있는데, <그림>에서 볼 수 있는 플래시 리모팅, 벨로시티, 웹 스타트, JSF가 바로 그것이다.

플래시 리모팅 MX
플래시 리모팅 기술은 2002년  펫 마켓(PetMarket)이라는 예제와 함께 처음 소개되었다. 초기에는 매크로미디어의 콜드퓨전 서버에서 CFML(ColdFusion Markup Language)를 이용해서 개발하던 방식을 사용했는데, 자바 기술과 연동될 수 있는 플래시 리모팅 MX가 발표되면서부터 자바 공동체의 뷰 기술의 한 가지 대안으로 자리매김 하였다. 플래시 리모팅 기술의 장점은 무엇보다 플래시를 이용해서 다이내믹한 사용자 인터페이스를 꾸밀 수 있다는 점과, 하나의 웹 페이지를 효율적으로 분할해서 페이지 이동 없이 한 페이지에서 모든 처리가 이루어지는 원 페이지 모델 기법이 구현 가능하다는 것이다. 플래시 리모팅은 인터넷 예약/예매 시스템과 같은 비교적 단순하면서 사용자의 편의성이 최대한 요구되는 웹 애플리케이션에서 적극적으로 도입되고 있는 추세이다.

X인터넷과 자바 웹 스타트
X인터넷은 2000년 IT 전문 조사기관인 포레스터 리서치에 의해 소개된 개념이다. X인터넷은 실행 가능한 인터넷(eXcutable Internet)과 확장된 인터넷(eXtended Internet)을 뜻하는 용어이다. 본지를 통해 현재 연재되고 있는 Curl이 X인터넷 기술의 대표적인 예다. 실행 가능한 인터넷이란 쉽게 말해 웹을 기반으로 한 C/S 환경의 복귀라고 말할 수 있다. Curl 프로그래밍에서 볼 수 있듯이 X인터넷 기술은 마치 비주얼 베이직이나 델파이를 사용하는 것과 같이 편리한 인터페이스와 다양한 컴포넌트를 통해 높은 개발 생산성을 보장받을 수 있다. 확장 가능한 인터넷은 유비쿼터스 시대의 도래와 밀접한 관계를 지닌다. 그것은 TV, 냉장고, 심지어 시계까지 인터넷 연결이 가능한 칩을 내장한 모든 기기의 상호 연결성을 강화한 개념을 의미하는 말이다. 자바 진영에서도 X인터넷처럼 C/S의 장점을 인터넷과 결합하고자 하는 시도가 있어 왔는데, 자바 웹 스타트 기술이 바로 그 대표적인 예라고 볼 수 있다. X인터넷과 웹 스타트는 이전의 C/S 환경과 같이 복잡한 UI를 필요로 하는 은행이나 증권사와 같은 금융권을 중심으로 활발하게 도입되고 있다.

JSF
JSF(Java Server Faces)는 썬에서 인정한 자바 표준 UI 프레임워크로 J2EE 1.4 SDK에 JSF 1.0이 포함되어 있다. 올해 샌프란시스코에서 개최된 2004 자바원 컨퍼런스에서도 JSF에 대한 개발자들의 관심과 호응은 특히 대단했다고 한다. JSF를 쉽게 이해하려면 닷넷의 웹폼(WebForm)과 비쥬얼 스튜디오를 떠올리면 된다. JSF를 이용하면 다양한 서버사이드 웹 컴포넌트를 활용해서 쉽고 빠르게 애플리케이션을 개발할 수 있고, 비즈니스 로직과의 매개를 자동화함으로써 개발자로 하여금 UI와 비즈니스 로직에 집중할 수 있는 뼈대를 마련해준다. 현재 썬의 자바 스튜디오 크리에이터를 비롯해 IBM의 웹스피어 스튜디오 등이 JSF를 지원하고 있다. JSF는 어떤 의미에서 성공이 보장된 기술이라고 볼 수 있지만, 툴의 안정화나 기술의 확산이 어느 정도 이루어지기까지는 아직 시기상조인 기술로 보인다. 하지만 웹 서비스의 사용이 본격화되는 시점에서는 자바 뷰 기술에서도 가장 확고하게 자리를 잡을 것으로 보이기 때문에 꾸준히 관심을 가져야 할 필요가 있을 것 같다.

벨로시티
벨로시티는 자카르타 프로젝트에서 진행 중인 자바 기반의 템플릿 엔진이다. 템플릿(디자인)과 코드(로직)을 따로 개발한 다음 템플릿 엔진인 벨로시티에 보내게 되면 HTML을 비롯한 다양한 양식의 결과 페이지를 렌더링해서 출력하게 된다. 본 연재에서 함께 개발하게 될 VSSH 프레임워크는 앞서 소개한 다양한 뷰 처리 기법 중 벨로시티를 사용해서 오픈소스 프레임워크를 완성하게 될 것이다. 벨로시티에 대해서는 이미 지난 2003년 1월호부터 2회에 걸쳐 연재된 바 있다.

 

출처 : 마이크로소프트(마소 2004.9월)

 

1회, 오픈소스를 이용한 시스템 통합, 그 즐거운 도전의 시작
2회, 첫번째 도전! 스트럿츠와 벨로시티의 통합

3회, 약한 결합도 아키텍처를 위한 대안 기술, 스프링
4회, OR 맵핑 도구의 선두 주자, 하이버네이트

 

이번 기획은 값 비싼 상용 웹 애플리케이션 서버나 개발 도구 등과 비교해 결코 뒤지지 않는 J2EE 기반 오픈소스만을 활용해 경쟁력 있는 시스템 통합이 가능하다는 것을 보여주기 위해 준비됐다. 이를 통해 그동안 오픈소스에 대한 잘못된 인식을 바로 잡고, 오픈소스로도 실제 e비즈니스 시장을 성공적으로 공략할 수 있다는 것을 보여주고자 한다. 또한 연재와 함께 웹을 통해 오픈소스 프로젝트가 진행되고 있으니 오픈소스에 관심이 있는 독자 여러분들의 많은 참여 바란다. <편집자 주>

최근 IT 경기 침체로 인해 해외 시장으로 눈을 돌리고, 기업 경쟁력을 재고하게 되면서부터 대형 SI 업체를 중심으로 바람직한 변화의 바람이 불고 있다. 기업 내의 CMO 조직을 중심으로 CMM, SPICE, CMMI에 기반 한 조직 프로세스 개선이 이루어지고, 프로젝트 성공률을 높이기 위한 다양한 PM 기법들이 도입되며, 프레임워크 도입과 방법론 적용을 통해 자체 솔루션의 경쟁력을 높이기 위한 다양한 연구가 활발하게 진행되고 있다. 하지만 그러한 노력에도 불구하고 실제로 국내 SI 프로젝트의 수준은 비약적인 기술 발전을 이루어내지 못하고 있다. 그 이유 중 하나는 이러한 변화를 이끌고 있는 리더 그룹과 실제 개발자들 사이의 기술 격차가 너무 크다는 데 있다. 또한 자금력이 부족한 중소규모 SI 업체들은 이러한 변화를 수용하기에 충분한 연구 인력도 없고, 기술 지원도 받지 못하고 있는 실정이다.

##########0*
<그림 1> 오픈소스 통합 배포판이 왜 필요한가?

논쟁의 중심에 선 오픈소스
최근 비하이브(Beehive)라고 명명된 BEA의 오픈소스 프로젝트와 2004 자바원 컨퍼런스에서 논의된 '오픈소스 자바'가 IT 업계의 핫이슈로 떠오르고 있다. 자바 개발자 커뮤니티의 규모를 키움으로써 MS 닷넷 기술의 거센 도전에 대응하겠다는 전략적 판단과 단일 기업인 MS에 비해 JCP가 가지는 느린 의사 결정의 한계를 극복하는 방안으로 오픈소스가 고려되고 있는 것이다. 비록 IBM, 썬, BEA, 오라클 등과 같은 자바 진영에 포진한 메이저 회사들이 오픈소스를 이용한 정책 변화에 한 목소리를 내고 있지는 않지만, 최소한 자바 커뮤니티는 많은 기업들에게 자바와 닷넷의 선택은 ‘둘 다’가 아닌 ‘둘 중 하나’가 되리라는 것을 알고 경쟁력 강화에 힘을 쏟고 있는 듯하다. 그러한 상황에서 자바가 지금과 같은 위치를 계속 고수할 수 있는지의 여부는 중소기업 시장을 장악할 수 있느냐와 개발자(개발 회사)들에게 자바가 여전히 매력적인 기술임을 확신시킬 수 있느냐에 달렸다고 볼 수 있다.

J2EE 관련 오픈소스 기술
IT 개발환경은 경기 침체로 인해 오히려 바람직한 변화를 겪고 있다. 하지만 이러한 변화를 이끌고 있는 선두 그룹과 일반 개발자들 사이에 너무 큰 기술 격차가 존재함으로써 변화로 인한 효과는 크지 않은 실정이다. 또한 닷넷의 거센 도전에 대항하기 위해 자바 진영에서는 오픈소스를 활용한 다양한 해결책을 제시하고 있고 그것은 업계의 큰 관심과 다양한 논쟁을 불러일으키고 있다. 하지만 국내에서는 오픈소스가 지닌 커다란 가능성에 대해 완전히 수긍하지 못하고 있는 분위기다. 그 이유는 오픈소스의 장점을 제대로 보여줄 수 있는 가이드라인이 부재하다는 데 있다.

초창기 리눅스는 소수의 전문가 그룹의 전유물이었고, 유닉스를 위협할 단지 가능성 있는 기술에 불과했다. 그러나 레드햇과 같은 배포판이 복잡도를 감추고 일반인에게 쉽게 다가갈 수 있도록 흩어져 있는 오픈소스들을 하나로 통합함으로써 비로소 운영체제로써 자신만의 확고한 영역을 가질 수 있게 되었다. 필자는 바로 이러한 배포판이 다양하고 강력한 오픈소스 기술을 많이 확보하고 있는 J2EE 개발 분야에 꼭 필요하다고 생각한다.

자바를 지원하는 오픈소스 도구로 검증받은 우수한 솔루션이 너무나 많이 존재하고 있다. 톰캣, Resin, Enhydra 와 같은 다양한 JSP 컨테이너가 있고, JBoss, JOnAs와 같은 EJB 컨테이너도 있으며, 이클립스, NetBeans와 같은 우수한 IDE와 수많은 플러그인들이 있다. JUnit, CVS, Ant, log4j와 같은 오픈소스 기술들은 이미 일반화된 지 오래고, Struts, Turbine, Velocity, Spring과 같은 공개된 프레임워크와 Castor, Hibernate와 같은 O/R 맵퍼, MDA 지원을 위한 AndroMDA까지 그 자체로도 충분히 경쟁력 있는 개발 환경이 구축될 수 있을 만큼 오픈소스 시장은 성숙해 있다. 이제 개발자에게 필요한 것은 Struts가 MVC 개념을 지원할 기본 프레임워크로 훌륭하다거나, 이클립스를 쓰면 상용 자바 IDE 툴 못지않게 개발 생산성이 높아진다거나, Hibernate가 엔티티 빈이 갖는 약점들을 극복해줄 대안이 될 수 있다거나 하는 단편적인 지식이 아니다. 다양한 대안들 중에서 한 가지 기술이나 툴을 선택하고, 그것이 자신이 수행하는 프로젝트에 어떻게 적용되어야 할지 고민하는 그 순간에 이미 경쟁력을 상실할 수 있을 만큼 기술은 빠르게 변화하고 짧은 시장진입기간(Time to market)이 요구되기 때문이다. 외국의 경우 J2EE 기반 기술을 주로 다루는 오픈소스 컨설팅 기업들이 이러한 고민을 덜어 줄 다양한 활동을 펼치고 있지만, 안타깝게도 국내에는 그러한 사례가 전혀 없다.

<그림 2> J2EE 기반 오픈소스 통합 배포판의 개념

<그림 2>는 필자가 생각하고 있는 J2EE 기반 기술을 위한 오픈소스 통합 배포판의 개념을 정리한 것이다. 우선 다양한 오픈소스 기술이나 제품들 중에서 가장 최적의 조합을 선택해 미리 설정된 형태로 압축해서 배포한다. 다행인 점은 자바의 특징상 리눅스의 아나콘다와 같은 별도의 설치 프로그램이 필요 없다는 것이다. 단지 압축을 풀고, 필요한 환경 변수 몇 가지를 설정해주는 것만으로 설치를 끝낼 수 있다.

배포판에 포함된 모든 기술을 필요로 하지 않을 경우도 있다. 그래서 필요한 항목을 선택한 다음 내장된 Ant를 이용해서 간단하게 빌드할 수 있도록 설정해 두는 것이 좋다. 오픈소스 통합 배포판에서 가장 중요한 것은 미리 경험한 다양한 문제에 대한 경험을 함께 전달해야 한다는 것이다. 그래서 오픈소스 통합 배포판에 포함된 기술들을 사용해서 프로젝트를 진행하는 방법론과 그 방법론이 적용된 적당한 규모의 예제가 포함되어야 할 것이다. 오픈소스는 결국 개발자들의 자발적인 관심과 참여로 성장하게 된다. 따라서 개발자를 아우를 수 있는 커뮤니티의 지원 역시 필수적인 요소라 할 수 있다.

이를 위해 이번 기획에서는 VSSH(Velocity+Struts+Spring+Hibernate)로 명명된 최초의 오픈소스 통합 배포판을 독자 여러분과 함께 개발해 나가고자 한다. 총 8회의 연재 중 1부는 VSSH의 근간을 이루는 VSSH 프레임워크의 개발에 초점을 맞추어 진행될 것이며, 2부는 완성된 VSSH 프레임워크를 이용해서 온라인 경매 프로그램을 개발하는 과정을 통해 다양한 오픈소스 도구들이 어떻게 활용될 수 있는지에 대한 가이드라인을 제시하게 될 것이다. 이를 위해 작은 포럼을 오픈했다. 방대한 주제를 짧은 지면에 다 소개할 수 없기 때문에 기사에서 미처 다루지 못한 내용들은 포럼을 통해 알려드릴 예정이므로 관심이 있는 독자는 꼭 방문해 이 프로젝트에 적극 참여해 주기 바란다.  나눌수록 깊어지는 게 어디 정 뿐이겠는가.

<화면 1> VSSH 포럼(http://java.techedu.net/phpBB2)

오픈소스를 이용한 프레임워크 개발 사례
사실 오픈소스 통합 배포판이란 용어가 사용된 적은 없지만 이 아이디어는 전혀 새로운 것이 아니다. 이미 국내외에서 이와 유사한 연구가 진행되고 있으며, 실무에 활용되고 있는 것도 많다. 여기서 잠시 이번 연재와 관련된 다양한 국내외 사례에 대해 살펴보도록 하겠다.

해외 사례, EJOSA와 OnJava
지난 달 본지에서 필자가 소개한 EJOSA(Enterprise Java Open Source Architecture)는 오픈소스 통합 배포판에 대한 직접적인 아이디어를 제공했다. EJOSA는 오픈소스만을 이용해서 구현된 J2EE 기반의 엔터프라이즈용 아키텍처이다. Enhydra와 JOnAs에서 동작하도록 설정되어 있으며, DB는 Firebiard DBMS를 사용한다. 개발 도구는 NetBeans를 추천하고 있고, 기본 프레임워크는 Velocity, AndroMDA, 세션 빈과 연동되는 Hibernate 기술을 사용하고 있다.

<그림 3> EJOSA의 구조도

EJOSA의 첫 번째 장점은 개발자가 오픈소스를 찾아다니는 수고를 덜어주었다는 점이다. EJOSA 내에는 앞에서 설명한 각종 툴과 프레임워크 외에도 Ant, XDoclet, JUnit, AspectJ, JDom, Lucene 등 개발에 필요한 각종 오픈소스들이 미리 설정된 채로 통합되어 있다. 개발자로 하여금 단지 매뉴얼에 나와 있는 대로 설정 파일을 수정하고, Ant를 실행하는 것만으로 필요한 도구들을 사용할 수 있게 도와준다. 두 번째 장점은 방법론과 예제, 경험이 포함된 풍부한 문서를 제공하고 있다는 것이다. 단지 각종 오픈소스들을 압축해서 배포하는 것이 아니라, 모델 중심의 개발 방법론을 EJOSA를 이용해서 실천하는 방법이 각종 도구의 사용법과 함께 친절하게 설명되어 있고, 그 방법론에 따라 개발된 각종 예제가 문서와 함께 공개되어 있다. 하지만 Enhydra나 JOnAs와 같이 EJOSA에 기본으로 탑재된 도구들은 국내에서 관심을 얻고 있지 못한 이질적인 것이었고, MDA라는 방법론 자체가 아직 국내에 도입되기에는 이른 감이 있었다.

때마침 오라일리가 운영하는 OnJava.com에서 「Wiring Your Web Application with Open Source Java」라는 기사가 등록되었다. EJOSA는 WAS나 개발도구와 같은 오픈소스 도구들을 중심으로 통합이 이루어진데 반해, OnJava에 소개된 내용은 별도의 프레임워크로 이미 널리 알려져 있는 Struts, Spring, Hibernate를 통합하고자 시도하고 있다.

<그림 4> Struts, Spring, Hibernate 프레임워크를 결합한 아키텍처 개요도

참고로 우리가 함께 개발하게 될 VSSH는 EJOSA의 아이디어를 기본 토대로 해서 국내 개발자들에게 친숙한 오픈소스 도구들이 구성에 포함될 것이며, OnJava.com에 소개된 것과 같이 유명 프레임워크들을 통합한 프레임워크가 제공될 것이다.

국내 사례, SAF와 JCF
국내에서 개발된 많은 프레임워크는 썬의 블루프린트에 포함된 WAF(Web Application Framework)와 아파치 자카르타의 Struts가 가장 많은 영향을 끼쳤다. SK C&C의 JGarnet이나, 컴포넌트베이시스의 CB*Framework와 같은 프레임워크 소개서에는 WAF와 Struts를 토대로 개발되었음이 명시되어 있다. 특히 올해 5월 KCSC(한국소프트웨어컨소시엄)의 'SW Architecture & Framework 개발 및 프로젝트 적용 사례 세미나'에서는 오픈소스 프레임워크를 결합하여 자사의 독창적인 프레임워크를 구현하고자 하는 시도가 보고되었다. 그 중에서 대표적인 2개의 사례에 대해 간단히 살펴보자.
처음 소개할 내용은 세림정보기술의 SAF(Selim Application Framework)이다. 세림은 초기에 디자인 패턴을 이용한 프레임워크를 자체 개발했으나, 높은 유지보수 비용으로 인해 오픈소스인 Struts를 활용하는 쪽으로 방향을 선회했다. 이것은 Struts가 발전하면 SAF도 함께 진화하는 것을 뜻한다. SAF는 기본적으로 <그림 5>와 같이 Struts를 활용한 파이프라인 아키텍처를 채택하고 있다.

<그림 5> SAF의 파이프라인 아키텍처

SAF의 특징은 디자인 패턴을 중심으로 Struts를 확장하여 WAF와 유사한 아키텍처를 지니고 있다는 것이다. SAF의 현재 버전은 뷰 처리를 Velocity 및 Tiles를 이용해서 구현하고 있고, XML에 기반한 DAO 패턴을 자체 구현하여 영속성 처리를 하고 있는데, 다음 버전에는 JSF와 다양한 O/R 맵퍼를 연동할 계획을 갖고 있다고 한다.

<그림 6> SAF의 최종 아키텍처

두 번째 소개할 내용은 대우정보시스템의 JCF(J2EE Core Framework)이다. JCF는 대우정보시스템의 J2EE 애플리케이션 개발을 위한 표준 애플리케이션 프레임워크로서, 분석, 설계, 구현, 테스트, 관리에 이르는 종단간(end-to-end) 개발 프로세스를 효과적으로 수행하기 위한 통합 프레임워크이다. 또한 J2EE 애플리케이션 개발의 전 공정에서 개발자들이 따라야 할 표준지침이기도 하다. JCF의 오픈소스의 활용 측면에서 주목할만한 측면을 많이 갖추고 있다.

<표 1> JCF의 구성

JCF의 구성에서 보듯이 JCF 역시 무게 중심은 오픈소스를 통합한 프레임워크 자체에 있다. 하지만 이클립스, Ant, JUnit, XDoclet 등의 오픈소스 도구들이 개발에 활용되고 있다는 점과 그것을 효과적으로 뒷받침하는 개발 표준 및 개발지침, 활용 경험 등이 문서화되어 가이드라인으로 제공되고 있다는 점에 주목해야 한다. 또한 프레임워크에서 Spring이 무게중심에 있다는 것도 특이한 점이다. Spring은 비즈니스 로직 처리에 강점이 많은 Struts와 같은 오픈소스 프레임워크이다. Spring 프레임워크는 3회 연재에서 자세히 다룰 것이므로 우선 Struts나 Hibernate와 같은 기존 기술과 경쟁적이지 않은 자연스러운 협력이 가능한 구조를 갖추고 있다는 것 정도만 알아두도록 하자.
지금까지 오픈소스 통합 배포판과 관련한 국내외 관련 사례들을 간단히 살펴보았다. 해외의 사례들은 오픈소스 도구의 활용에 많은 강점을 지니고 있고, 오픈소스 기술의 통합 자체에도 많은 관심을 쏟고 있다.

반면에 국내의 사례들은 주로 검증받은 상용 도구를 이용해서 CBD 방법론을 적용한 시스템 통합에 사용될 프레임워크에 관심이 집중되어 있음을 알 수 있다. 특히 Struts, Velocity 등의 프레임워크 통합과 관련해서는 외국에 뒤쳐지지 않은 많은 연구가 이미 진행되어 있고, 이러한 사례들이 2003년을 기점으로 폭발적으로 증가하고 있다는 사실은 기억해 둘만 하다. 이것은 자바 기반의 오픈소스 프로젝트들이 실무에 충분히 활용될 수 있을 만큼 안정화되고 있다는 걸 뜻하며, 사용 저변이 크게 확대되고 있음을 반증하는 것이기도 하다. 또한 각 기업의 발표 자료를 통해 공공, 금융, 제조를 비롯한 각종 e비즈니스 시스템 구축에 이미 다양한 오픈소스 기술들이 침투해 있다는 사실도 확인할 수 있었다. 덕분에 오픈소스 기술을 활용한 레퍼런스를 확보하는 작업이 그리 어렵지 않을 듯 하다.

오픈소스 통합 배포판 프로젝트 VSSH
앞서 얘기한 대로 VSSH는 지금부터 우리가 함께 만들어 나갈 오픈소스 통합 배포판의 프로젝트 이름이다. VSSH는 전체적으로 <그림 7>과 같이 구성된다. 지금부터 VSSH의 구성 요소들에 대해 간단히 알아보도록 하자.

<그림 7> VSSH의 구성

VSSH 프레임워크
VSSH의 가장 핵심적인 부분으로 Velocity, Struts, Spring, Hibernate로 구성된 프레임워크이다. 앞으로 4회에 걸쳐 이 부분을 집중적으로 다루게 될 것이다. J2EE 애플리케이션은 크게 프리젠테이션, 제어, 비즈니스 로직, 데이터 처리라는 4개의 레이어로 구성된다. 각각은 이미 독자적인 사용자 층을 확보하고 있는 오픈소스 기술들이지만, VSSH에서 Velocity는 사용자 뷰를 결정짓는 프리젠테이션 계층에서 사용되고, Struts는 제어 계층, Spring은 비즈니스 로직 계층, Hibernate는 데이터 처리 계층을 담당하게 될 것이다. VSSH 프레임워크는 적당한 계층에 각 기술을 단순히 나열하는 데 그치는 게 아니라 가장 효율적인 통합 방안을 함께 고민할 것이다. 2회에서는 Struts와 Velocity를 연결하는 방법에 대해 살펴보고, 3회에서는 Struts와 Spring을 결합하는 방법 및 Spring에서 비즈니스 로직을 처리하는 가이드라인을 제공할 것이다. 마지막 4회에서는 Spring과 Hibernate를 통합하는 방법과 그 기반 위에서 컴포넌트를 구성하는 방법을 살펴볼 것이다. 연재가 끝날 때쯤이면 이달의 디스켓과 포럼을 통해 VSSH 프레임워크 및 사용 가이드를 다운받을 수 있도록 하겠다.

VSSH 환경
VSSH 환경은 완성된 VSSH 프레임워크가 동작되는 운영 환경과 개발 환경으로 구성된다. 개발 환경은 이클립스와 관련 플러그인으로 구성되며, 그와 대응되는 NetBeans에 대해서도 소개될 것이다. 동작 환경은 아파치 웹 서버와 톰캣이 내장된 JBoss가 사용되며, 데이터베이스는 HSQLDB를 사용할 예정이다. 그 밖에 JUnit, Ant, CVS, XDoclet, log4j가 기본적인 개발 환경에 포함된다. 저작권에 문제가 없는 범위 내에서 각 도구 및 관련 플러그인은 미리 설정된 형태로 압축시켜 배포할 예정이며, 환경 설정을 끝낸 프로젝트 파일이 샘플로 포함될 것이다. VSSH의 나머지 부분은 2부에서 다루게 될 것이다.

Open CBD 방법론
CBD 방법론이라고 해서 거창한 걸 준비하고 있는 것은 아니다. 개발팀이 10~30명 수준이라고 가정하고, CBD96과 UML Component, 마르미 III를 토대로 가벼운 CBD 개발 프로세스를 정립해서 VSSH 사용에 대한 가이드라인을 제공하고자 한다. 여기에는 포함된 각 오픈소스 기술의 매뉴얼과 산출물 양식 및 적용 샘플, 컴포넌트 표준 스펙 및 코딩 규칙 등이 포함될 것이다. 이미 자사의 개발 방법론을 갖춘 팀에게는 매력이 없을 수도 있겠지만 VSSH를 이용하고자 하는 소규모 개발팀에게는 가장 필요한 내용일 것이다.

VSSH 활용 샘플
1부는 이론적인 설명이 많은 관계로 회원가입 및 로그인/아웃을 처리하는 간단한 예제를 가지고 진행할 것이다. 하지만 적당히 규모가 있는 샘플 애플리케이션이 없으면 VSSH가 완성되더라도 참조 모델이 없어 막막할 것이다. 따라서 2부의 내용은 VSSH를 이용해서 가칭 OpenAuction이라고 이름 지은 온라인 경매시스템 개발 과정을 따라가며 진행할 생각이다.

VSSH 프레임워크를 구성하는 각 기술의 선정 배경
VSSH를 준비하면서 가장 고민했던 부분은 서로 다른 장단점을 갖고 있는 수많은 오픈소스 도구들 중에서 어떤 것을 선택하느냐 하는 문제였다. VSSH는 특정 오픈소스 기술을 설명하려는 것이 아니라 오픈소스를 중심으로 SI 프로젝트가 진행되기 위한 가이드라인을 제공하는 것이 목적이기 때문이다. VSSH 프레임워크를 구성하는 각 요소 기술들은 각 영역별로 국내에서 가장 많이 알려진 것을 선정하기 위해 노력했다. 그 이유는 특별히 기술 지원을 약속할 업체가 없는 오픈소스 기술이기 때문에 참고할 자료가 많아야 유리하다고 판단했기 때문이다. 이 글을 읽고 있는 독자들도 이 연재에서 다루는 모든 기술을 짧은 기사를 통해 모두 익히겠다는 욕심은 버렸으면 한다(기사에서 다루지 못한 각 요소 기술에 대한 참고자료들을 아이마소와 포럼을 통해 꾸준히 등록할 수 있도록 노력할 계획이다). 그보다 각 기술이 어떻게 결합되어 시너지 효과를 발휘하는지, 그리고 어떻게 실제 프로젝트에 적용 가능한지를 중심으로 기사를 읽어 나가면 좋겠다. "난 Turbine의 기능이 더 뛰어나다고 생각하는데 왜 하필 Struts를 쓰는거야?"라든가, "OJB가 썬의 JDO 스펙을 준수하는데다 자카르타 프로젝트로 진행되는데 Hibernate를 포함시킨 근거는 뭐야?"와 같은 딴지는 사양하는 바이다. 필자는 자신 있게 무엇이 더 훌륭하다고 내세울 만큼 실력이나 안목이 뛰어난 사람이 아니다. 다만 지금 이 시기에 VSSH와 같은 시도가 꼭 필요하다고 생각되어 바쁜 일상을 쪼개 쥐어짜낸 한 웅큼의 용기를 손에 쥐고 다소 무모한 프로젝트를 시작한 여러분과 똑 같은 한 사람의 개발자일 뿐이다.

프리젠테이션 계층, Velocity
프리젠테이션 계층에 Velocity를 쓸 것인지를 두고 가장 많은 고민을 했다. 기존의 JSP에 JSTL이나 Struts 태그 라이브러리를 섞어 사용하는 것만으로도 충분히 로직과 분리된 뷰 개발이 가능하기 때문이다. 하지만 뷰는 개발자와 성격이 다른 웹 디자이너들과 공동 작업이 필요한 특수한 영역이기 때문에 드림위버와 궁합이 좋은 Velocity를 사용하기로 결심했다. 그로 인해 템플릿 엔진에 대한 개념을 이해해야 하고, 값 객체의 자동 맵핑을 위한 유틸리티 클래스 개발과 같은 추가적인 부담이 생겼지만 그것을 보상할 만한 충분한 이점이 있다고 믿는다.

제어 계층, Struts
제어 계층에 사용할 프레임워크를 Struts로 선정하는 것은 크게 어려운 일이 아니었다. Webwork2가 발표되었고, Turbine이나 Expresso와 같은 충분히 경쟁력 있는 프레임워크가 많이 존재하지만, 국내에서는 Struts가 확실한 입지를 굳힌 것 같다. 굳이 선정 배경을 이야기한다면, 많은 사용자 층에 의한 충분한 자료, 벤더들의 확실한 지원, 검증된 다양한 레퍼런스를 쉽게 구할 수 있다는 점이 Struts를 제어 계층에 사용한 이유다. 무엇보다 발생 가능한 다양한 문제들과 그 해결책이 이미 알려져 있어 다양한 프레임워크를 통합하고자 시도하는 VSSH 프레임워크의 안정성을 향상시키는 데 큰 도움이 되리라 생각한다.

비즈니스 로직 계층, Spring
비즈니스 로직 계층은 Spring을 사용할 것인지, 말 것인지를 결정해야 했다. EJB를 사용한다면 Spring을 사용하지 않고도 알려진 J2EE 패턴/EJB 패턴을 이용해서 WAS의 성능을 빌어 견고한 애플리케이션 개발이 가능하다. 하지만 EJB를 사용하지 않고 일반 자바 객체 POJO(Plain Of Java Object)만을 이용해서 개발할 경우까지 함께 고려한다면, 문제의 여지가 있었다. 또한 제어 계층의 Struts와 데이터 처리 계층의 Hibernate를 직접 연결하는 것은 자칫하면 계층간의 연결을 강하게 만들 위험이 있었으며, 컴포넌트 구현을 위한 기본 스펙을 만드는 것도 까다로웠다. Spring은 개발자인 Rod Johnson이 저술한 『Expert One-on-One J2EE Development without EJB』라는 책에 소개된 것처럼 EJB의 사용에 드는 비용을 줄여준다. Spring 프레임워크가 그 자체로써 경량급 컨테이너라고 불리는 이유는 EJB가 가지는 컨테이너의 의존성, 배포 비용을 줄여주기 때문이다. 이 점은 개발 도구와 WAS가 짝을 지어 사용되는 국내 개발 환경에서 Spring을 반드시 사용해야 할 이유가 되기에 충분했다. Spring은 WAS나 개발 환경, 연결된 다른 프레임워크에 대해 의존성이 낮은 비즈니스 로직을 개발해내기 위해 도입되었으며, 최근 관심을 끌고 있는 AOP(Aspect Oriented Programming)를 적용해볼 기회를 제공하기도 한다.

데이터 처리 계층, Hibernate
데이터 처리 계층에서 EJB 엔티티 빈은 처음부터 고려 대상에서 제외시켰다. 이는 EJB 2.0 스펙의 발표와 함께 욕심내어 진행한 프로젝트에서 엔티티 빈의 사용으로 인해 문제가 오히려 복잡해진 경험이 있었기 때문이다. 그 당시 CMP/CMR에 대한 개발 툴의 지원은 기대했던 것만큼 만족스럽지 못했고, EJBQL은 마치 미완성인 기술처럼 허점을 드러냈다. 결국 CRUD 처리를 위한 단순한 기능은 CMP 방식의 엔티티 빈으로 구현하고, 복잡하거나 성능이 중요한 부분은 JDBC를 사용해서 직접 DAO를 구성하는 방식으로 분할해서 구현한 다음, Business Delegate 객체를 만들어서 그 과정을 로직에 숨기는 것으로 만족해야 했다. 지금은 많은 부분이 개선됐지만, 그러한 경험은 자연스럽게 엔티티 빈의 대안 기술인 O/R 맵퍼에 관심을 두게 만들었다. 초기에는 JDO(Java Data Object) 스펙을 구현한 자카르타의 OJB와 Castor JDO를 고려해 보았다. 하지만 이클립스를 비롯한 통합개발환경의 지원이 미약해서 생산성이 떨어지는 문제가 있었다. 그래서 상용 제품과 비교해 기능적으로 뒤쳐지지도 않으면서, 다양한 기존 프레임워크와 연동이 쉽고 Hibern8IDE를 비롯한 각종 개발 도구가 공개되어 있는 Hibernate를 최종적으로 선택하게 되었다. 또한 Hibernate는 효율적인 개발을 돕는 풍부한 레퍼런스를 가지고 있으며, 커뮤니티의 지원도 훌륭했다. Hibernate의 사용은 추후 JDO의 구현이 활성화되더라도 O/R 맵퍼에 대한 이해를 바탕으로 쉽게 적응할 수 있는 장점도 갖추고 있다.

VSSH 제어 계층을 구성하는 Struts
이제 VSSH 프레임워크에서 모든 제어를 관리할 Struts에 대해 살펴보도록 하자. 프레임워크의 필요성이나, Struts에 대한 설치 및 활용 방법은 이미 널리 알려져 있으므로 생략하겠다. 기본적인 내용에 대해 도움이 필요한 독자는 VSSH 포럼을 통해 질문을 올려주기 바란다. 여기서는 Struts 그 자체보다 Struts 내에 적용된 디자인 패턴을 이해함으로써 프레임워크에서 제어가 이루어지는 기본적인 원리를 알아보도록 하겠다. 그 과정을 통해 Struts를 이용해서 다양한 응용을 시도할 수 있는 내공이 쌓이기를 바란다.

Struts 구현에 사용된 J2EE 패턴
<표 2>는 Struts 구현에 사용된 J2EE 패턴과 그 패턴이 구현된 클래스를 정리한 것이다.

<표 2> Struts에 사용된 패턴 및 그 패턴이 구현된 컴포넌트

여기서 가장 핵심이 되는 것은 Front Controller 패턴과 View Helper 패턴이 결합된 일종의 매크로 패턴인 Service to Worker 패턴이다.

Service to Worker 패턴
Service to Worker 패턴은 블루프린트에 소개된 Core J2EE Pattern의 하나로, Struts의 제어 과정을 이해하기 위해 반드시 학습해야 하는 기본 패턴이라고 할 수 있다. <그림 8>은 이 패턴의 클래스 다이어그램이다.

<그림 8> Service to Worker 패턴의 클래스 다이어그램

여기서 주목할 것은 클라이언트의 모든 요청이 예외 없이 Controller라고 이름 붙은 서블릿으로 전달되고 있다는 것이다. 이를 위해 web.xml에서 다음과 같이 *.do로 끝나는 모든 URL에 대해서 ActionServlet이 책임을 지도록 서블릿 맵핑을 설정해 줘야 한다.

<servlet>
    <servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

클라이언트의 요청을 처리하기 위한 중앙 집중적인 접근 포인트를 제공하는 이러한 설계 패턴을 Front Controller라고 부르는데, 이 점은 모델 2 방식의 J2EE 개발에 있어 가장 핵심이 되는 특징이다. *가 포함된 URL 패턴이 특정 서블릿과 연결되어 있다면 Front Controller 패턴이 적용된 것이라고 생각해도 좋을 것이다. Front Controller 패턴을 사용하면 시스템 서비스, 로그 기록, 보안, 뷰 관리, 페이지 이동 등의 모든 코드를 한 곳에서 관리하기 때문에, 코드의 중복을 방지할 수 있는 이점과 함께 뷰와 애플리케이션 로직을 분리시킴으로써 코드의 유지 보수가 쉬워지는 이점이 있다. Front Controller는 JSP로 개발하는 JSPFront 방식과 서블릿으로 개발하는 ServletFront 방식으로 나뉘는데, 서블릿을 Front Controller로 사용하는 것이 일반적이다.

Controller
Struts에서 요청을 처리하는 시작점이 되는 것은 ActionServlet인데, 내부적으로 ActionServlet의 동작은 RequestProcessor의 process() 메쏘드를 실행시킴으로써 이루어진다. 따라서 Controller를 확장할 때는 ActionServlet을 직접 확장하는 것보다 RequestProcessor를 확장한 새로운 클래스를 만들고, struts-config.xml에서 controller 요소에 processorClass 속성을 지정해서 변경하는 것이 좋다. RequestProcessor는 다음과 같은 15 단계를 통해 동작한다.

01. processPath() : ActionMapping에서 사용할 URL Path 처리
02. processLocale() : 국제화 지원을 위한 언어 로케일 처리
03. processContent() : 기본 문서 타입 지정(초기값 : text/html)
04. processNoCache() : HTTP 헤더를 설정해서 요청이 캐시되는 것을 막음
05. processPreprocess() : 기능 확장을 위한 예비 메소드
06. processMapping() : 앞에서 구한 Path를 이용해서 실행할 ActionMapping 결정
07. processRoles() : 보안을 위한 접근 제어 목적으로 설정된 Role 확인
08. processActionForm() : ActionMapping과 연결된 폼빈(ActionForm) 초기화
09. processPopulate() : 폼 빈에 값을 채워넣음
10. processValidate() : 폼 빈의 입력값에 대한 유효성 체크 - validate() 호출
11. processForward() : 액션 맵핑에 forward 지정이 된 경우 해당 파일로 포워드
12. processInclude() : 액션 맵핑에 include 지정이 된 경우, 실행 결과를 응답에 포함
13. processActionCreate() : Action 클래스 인스턴스 생성
14. processActionPerform() : Action 클래스 실행 - execute() 메쏘드 호출
15. processActionForward() : Action의 실행결과인 ActionForward에 의해 제어 이동

이 과정 중 제어의 이동과 관련된 부분은 01, 06, 11, 13~15의 6개 과정이다. 다음과 같이 struts-config.xml이 구성되어 있다고 가정하자(이처럼 FrontController에서 제어를 관리하는 데 필요한 자원 맵핑 정보를 설정 파일을 통해 논리적으로 유지하는 것을 Logical Resource Mapping 전략이라고 한다). http://java.techedu.net/login.do와 같이 호출될 경우 첫 번째 단계인 processPath()에서 URL과 *.do가 제거된 login이 path로 결정된다. 결정된 path 정보를 가지고 6번째 단계인 processMapping()에서 <action-mappings>에 설정된 action을 결정해서 ActionMapping 클래스를 만들게 된다. 만일 처리 요청이 단순 포워드라면, 11번째 단계를 통해 Action을 통하지 않고 바로 해당 JSP 파일로 포워드된다. 그렇지 않다면 13번째에서 15번째 과정을 통해 Action이 만들어지고, 실행되며 그 결과인 ActionForward에서 지정한 곳으로 결과를 포워드시키는 것이다.

<struts-config>
    <global-forwards>
        <forward name="main" path="/main.jsp" />
    </global-forwards>
    <action-mappings>
        <action path="/main" forward="/main.jsp" />
        <action path="/login" type="com.seal.LoginAction" scope="request" input="login.html">
            <forward name="admin" path="/member.jsp" />
        </action>
<action-mappings>
</struts-config>

Dispatcher
Struts는 네비게이션과 뷰 관리를 담당할 Dispatcher 컴포넌트로써 Action 클래스를 사용하는데, 요청에 관련된 세부 사항들을 Action에게 알리고 Action이 응답을 책임지도록 위임한다. 이것을 FrontController의 "Command and Controller" 전략이라고 부른다. 이 전략은 기존에 잘 알려진 Command 패턴에 Front Controller와 결합한 제어의 이동 역할을 더한 것이라고 볼 수 있다.

Helper
Service to Worker 패턴에서 Dispatcher는 Helper를 사용하여 뷰 역할을 수행하는 JSP로 데이터를 보낸다. 비즈니스 데이터를 자바빈즈 형태의 뷰 컴포넌트로 만들어 JSP로 넘기는 이러한 구조를 View Helper 패턴이라고 한다. Struts에서는 이러한 Helper 역할을 ActionForm이 담당하고 있다. 지금까지 설명한 Service to Worker 패턴의 동작 순서를 시퀀스 다이어그램으로 표시하면 <그림 9>와 같다.

<그림 9> Service to Worker 패턴의 시퀀스 다이어그램


Service to Worker 패턴의 동작 과정을 코드로 이해하고 싶다면, Manning에서 출판된 『Web Development with JSP』라는 책의 9장에 나와 있는 FAQ 시스템 구축 예제를 살펴보길 권하고 싶다. FAQ 예제에는 Command and Controller 전략이 적용된 Service to Worker 패턴을 구현한 서블릿 방식의 FrontController가 포함되어 있다. 또한 웹의 동기화로 인한 약점을 극복할 수 있도록 MD5 알고리즘이 적용된 Synchronizer Token 패턴도 구현되어 있다. Manning의 책은 인포북에서 번역되어 한글판으로도 나오고 있으므로 꼭 한번 예제를 따라해 보기 바란다.

독자의 참여가 프로젝트 성공의 열쇠다
‘오픈소스를 이용한 시스템 통합’이라는 주제로 기사를 작성해 달라는 부탁을 받은 것이 정확히 3개월 전이였던 걸로 기억한다. 즐거운 도전이 될 것 같은 생각에 무심코 시작한 일이 관련 자료를 조사하고 기획을 다듬어나가는 과정에서 부담이 될 만큼 큰 프로젝트로 발전해 버리고 말았다. 필자는 오픈소스 기술이 온 세상을 뒤덮어야 한다고 주장하는 광신도는 아니다. 독자 여러분과 마찬가지로 지금껏 J빌더나 웹로직 워크˜

 

ORM 이란 무엇인가?

개발 이야기/ORM | 2007. 5. 7. 13:46
Posted by 시반

ORM(Object-Relational Mappings)라는 건 과연 무엇일까?

근래 많이 보고 듣는 단어이고 ORM Framework이나 Tool이니 하면서 Hibernate iBatis, Toplink등등의 이름들도 많이 듣게 된다. 과연 이 ORM이라는 것이 무엇이길래 왜 이렇게 많은 사람들의 입에 오르내리는 것일까?

 

ORM이라는 것을 단순하게 표현해보자면 객체와 관계와의 설정? 정도일까? 그럼 여기서 말하는 객체라는 것은 우리가 흔히 말하는 OOP(Object-Oriented Programming)의 그 객체를 이야기 하는 것 이라면, 과연 관계라는 것이 의미하는 것은 무엇일까? 뭐 지극히 기초적인 이야기지만 우리(개발자)가 흔히 사용하고 있는 관계형 데이터베이스를 의미한다.

 

그렇다면 도대체 무엇이 문제여서 객체와 관계형 데이터베이스 간의 매핑을 지원해주는 Framework이나 Tool들이 나오는 것일까? 이미 우리는 OOP를 하면서 객체와 관계형 데이터베이스를 모두 잘 사용하고 있지 않은가? 그런데도 굳이 이런 새로운 개념들이 나오게 되는 이유는 흔히 말해서 Back to basics 라고 볼 수 있겠다. , 보다 OOP다운 프로그래밍을 하자는 데 에서부터 출발한 것이다.

 

그럼 과연 무엇이 문제였던 것일까? 생각해보면 당연하게 문제가 있을 수 밖에 없다고 생각되어진다. 스스로 한번 생각해보자. 우리가 어떤 어플리케이션을 만든다고 할 때 관련된 정보들은 객체에 담고 있게 된다. 많이들 예를 드는 주소록을 만든다고 생각을 해보자. 일단은 주소록의 주체가 될 사람이라는 객체가 있다고 가정해보면 주민등록번호, 이름, , 몸무게 등등이 저장될 것이다. 그리고 주소라던지 전화번호등이 저장 될 다른 객체(여기서는 주소만을 가정해보고 주소라는 객체라고 가정한다.)도 있게 될 것이다. 그럼 이것을 영구적으로 저장하기 위해서 파일이나 데이터베이스에 입력을 한다고 하면, 객체와 객체들의 관계를 데이터베이스의 테이블에 저장을 하게 된다는 말과 동일하게 된다. table들에 객체가 가지고 있던 정보를 입력하고 이 table들을 join과 같은 sql query문을 통해서 관계를 설정해주게 된다. 여기서 문제는 이 table들과 객체간의 이질성이 발생을 한다는 것이다.

 

보통 ORM Framework들은 이러한 이질성을 해결하기 위해서 객체와 table간의 관계를 설정하여 자동으로 처리를 해준다는 것이다. 개인적으로 많은 ORM Framework를 접해본 것이 아니라서 어떤 방법들이 사용되는지는 잘 모르겠지만, 예를 들어서 눈으로 확인해보자면 다음과 같은 Person이라는 객체가 있다고 가정한다.


public class Person {

   private String name;

   private String height;

   private String weight;

   private String ssn;

  // implement getter & setter methods

}



iBatis의 경우에는 다음과 같이 mapping file내에서 해당 query의 결과를 받을 객체를 지정해 줄 수 있다.

<select id="getPerson" resultClass="net.agilejava.person.domain.Person">
 
  SELECT name, height, weight, ssn FROM USER WHERE name = #name#;


</
select>

, getPerson라고 정의된 query의 결과는 net.agilejava.person.domain Person객체에 자동으로 mapping 되는 것이다. Hibernate의 경우에는 mapping 파일에서 다음과 같이 표현을 해준다.

<hibernate-mapping>

        <class name="net.agilejava.person.domain.Person" table=person”>

               <id name="name" column="name" />

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

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

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

        </class>

</hibernate-mapping>

위 두개의 Framework의 예시를 보면 알 수 있듯이 setter 메소드가 있다면 객체에 결과를 set하는 작업들이 따로 필요한 것이 아니라 자동으로 setting 되는 것이다. 물론 여기에 추가적으로 1:m 이나 m:1 등의 관계들이 형성되면 추가적인 작업이 필요하긴 하지만 어쨌든 일단 눈에 보이는 간단한 부분은 처리가 되는 것을 볼 수 있다. 물론 반대의 경우에도 객체를 던져주면 ORM Framework에서 알아서 get을 해와서 해당하는 column에 넣어주게 된다.

 

어떻게 보면 더 복잡해 보일 수 도 있는 ORM이지만 막상 사용해보면 그 편리함에 몸을 떨게 된다. 단순하게 get/set만 해주는게 목적이 아니라 객체지향적인 시스템을 위해서 관계형데이터베이스의 설계부터 변화를 주고, 설계된 데이터베이스와 객체와의 관계에 대한 설정 등을 포함하여 보다 객체지향적인 시스템의 완성을 위한 도구라고 말할 수 있겠다. 물론 ORM이라는 것이 흔히 말하는 silver bullet은 절.. 아니다. 이 녀석이 쓰여서 이득을 볼 수 있는 부분이 존재할 것이며, 쓰지 않아서 이득을 볼 부분이 존재 할 것이다. 많은 사람들이 ORM에 대하여 우려하고 있는 부분은 객체지향적으로 설계되지 않은 데이터베이스에서의 사용에 따른 폐혜라고 생각한다. 이미 데이터베이스 중심적인 사고를 통하여 만들어 놓은 데이터베이스에 ORM을 도입을 해서도 분명 이점이 있긴하겠지만, 그에 비해서 개발자들의 학습곡선 이라던지, 기존에 존재하는 코드나 시스템들과의 연계 또는 유지보수적인 측면, 그리고 성능 등에서 생각해보면 부정적으로 볼 수 밖에 없다. , 전체적인 시스템의 분석,설계 단계에서부터 객체와 데이터베이스를 따로 생각하는 것이 아니라 하나의 덩어리로 인지하고 양쪽 모두를 고려한 설계를 해나갈 수 있을 때, ORM은 보다 좋은 모습을 보여주고 각광을 받을 수 있을 것이다

 

출처 : evilimp's Blog

 
블로그 이미지

시반

시반(詩伴)이란 함께 시를 짓는 벗이란 뜻을 가지고 있습니다. 함께 나눌수 있는 그런 공간이길 바라며...

카테고리

분류 전체보기 (233)
개발 이야기 (73)
Java (22)
VoIP (19)
이클립스 (22)
ORM (6)
MINA (4)
WEB2.0 (57)
DB2 (24)
MySQL (6)
오라클 (26)
기타 (44)
취미 (0)
잡담 (2)