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

 

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

소스 : 다운로드

 
블로그 이미지

시반

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

카테고리

분류 전체보기 (233)
개발 이야기 (73)
WEB2.0 (57)
DB2 (24)
MySQL (6)
오라클 (26)
기타 (44)
취미 (0)
잡담 (2)