by 크캬캬

스프링 프레임워크를 사용한 데이터 접근
#

(하이버네이트의 예를 중심으로)
원문 : http://hibernate.org/110.html

Juergen Hoeller
2003년 7월


0. 서문 : 하이버네이트 사용자를 위한 스프링의 이점들
#

스프링의 가벼운 빈 컨테이너는 비지니스 객체들, DAO들, JDBC DataSources와 하이버네이트 SessionFactories와 같은 리소스들을 IoC 형태로 묶어준다. 커스텀 프라퍼티 파일들로부터 그 설정내용들을 읽어들이는 부분에 있어, XML 정의 어플리케이션 컨텍스트는 수동으로 관리되는 싱글톤 혹은 팩토리들에 대한 강력한 대체물일 것이다. 침입받지 않는 것이 핵심 목표이기 때문에, 스프링에 의해 설정되는 어플리케이션 빈들은 스프링 인터페이스 혹은 클래스들에 의존할 필요는 없지만, 그것들의 bean 프라퍼티들을 경유하여 설정된다. 이러한 개념은 어떠한 환경에서도 적용가능한데, J2EE 웹 어플리케이션, 데스크탑 어플리케이션, 혹은 심지어 애플릿에까지 확장된다.

하이버네이트 환경에서, DAO를 위해 제공되는 스프링의 포괄적인 트랜잭션 관리는 매우 흥미롭다. 목적은 데이터 접근과 트랜잭션 경계 영역을 분리하는 것인데, 이것은 특정한 데이터 접근 혹은 트랜잭션 전략을 가지지 않는 재사용 가능한 transactional 비지니스 객체 사용을 가능하게 한다. 경계는 TransactionTemplate를 사용한 프로그래밍적인 방법으로도 가능하고, AOP TransactionInterceptor를 경유하여 작성될 수도 있다. native 하이버네이트/JDBC 트랜잭션들과 JTA 모두 사용가능한 방법들이다. 이것은 로컬 비상태 세션 빈에 대한 실용적인 대안일 것이다.

스프링의 HibernateTemplate는 하이버네이트 기반 DAO를 구현하는 간편한 방법들을 제공하는데, 이것은 Session 인스턴스를 다루거나 혹은 트랜잭션들(관리)에 참여하는 것에 신경쓰지 않도록 해준다. try-catch 블럭도 필요 없고, 트랜잭션 체크도 필요없다. 간단한 하이버네이트 접근 메써드가 한줄에 모든 것을 가능하게 해준다. 잡다한 DAO들을 묶어주는 것은, DAO 인터페이스와 트랜잭션 관리 모두의 관점에서 잘 작동한다. 예를 들어, 특정한 DAO들은 일반적인 JDBC에 기반하여 구현될 수 있으며, 익셉션 수동관리를 피하기 위해 스프링의 JdbcTemplate를 사용하는 것은 더욱 바람직한 방법일 것이다.

당신은 라이브러리 형식으로 제공되는 스프링의 수많은 특색있는 클래스들을 사용할 수 있으며, 그것들은 모두 재사용 가능한 JavaBeans의 묶음으로 설계되어 있다. 스프링이 완전한 어플리케이션 프레임워크로써 제공되지 않는다는 사실에 실망하지 마라! 어플리케이션 컨텍스트 개념은 스프링의 다른 특징들을 사용하기 위한 요구조건이 아니라 부가적인 이점으로 작용한다. 어떤 경우에서든지, 범위에 상관없이, 그런 기반구조를 혼자 개발하는 노력과 위험을 기울이기 이전에, 스프링 사용을 재고하는 것이 좋을 것이다.


1. 소개 : 리소스 관리
#

전형적인 비지니스 어플리케이션들은 종종 반복적인 리소스 관리 코드로 난잡하다. 많은 프로젝트들은 이러한 이슈에 대한 독자적인 해결책을 만들려고 하지만, 가끔씩 프로그래밍 상의 편의성을 위해 적절한 실패 관리를 희생시키곤 한다. 스프링은 적절한 리소스 제어를 위한 간단한 해결책을 강력하게 제시하는데, 이것은 템플릿화(다시 말해 callback 인터페이스를 가진 기반 클래스들)를 이용하거나 AOP 인터셉터들을 적용한 '제어의 역전(IoC)'이다. 이 기반 클래스들은 적절한 리소스 관리와 특정 API 익셉션들을 체크되지 않는 기반 익셉션 계층구조로 적절하게 변환하는 작업들을 관리한다.

스프링은 어떠한 데이터 접근 방식에도 적절한 DAO 익셉션 계층을 제공한다. 직접적인 JDBC 사용에서는, JdbcTemplate 클래스가 커넥션 관리를 담당하고, SQLException을 DataAccessException 계층으로 변환해준다. 물론, 데이터베이스 특유의 SQL 에러 코드들을 의미가 담긴 익셉션 클래스들로 변환하는 작업도 해준다. 이것은 JTA와 JDBC 트랜잭션을 각각 스프링 트랜잭션 매니저를 사용하여 모두 지원한다. 스프링은 HibernateTemplate/JdoTemplate, HibernateInterceptor/JdoInterceptor 그리고 Hibernate/JDO transaction manager를 통해, Hibernate와 JDO에 대한 지원 역시 제공한다.

(이 모든 것의) 주된 목적은, 어떠한 데이터 접근방식과 트랜잭션 기술을 사용한 방식에서도 명확한 어플리케이션 계층화와 어플리케이션 객체들 간의 약한 결합을 가능하게 하는 것이다. 더이상 비지니스 객체는 데이터 접근 혹은 트랜잭션 방식에 의존적이지 않을 것이고, 하드코딩된 리소스 검색이 없으며, 싱글톤 클래스를 재설정하는 어려움과 임의의 서비스 등록이 없게 될 것이다. 어플리케이션 객체들을 묶고 그것들을 재사용가능하고 가능한한 컨테이너 의존성으로부터 자유롭게 유지해주는 단 하나의 간단하고 일관된 접근방식을 제공해준다.

모든 개별적인 데이터 접근방식들은 단독으로 쓸 수 있지만, XML 기반의 설정과 스프링을 사용하지 않은 일반적인 JavaBean 인스턴스의 교차참조(cross-referencing)를 제공하는 스프링의 어플리케이션 컨텍스트 개념에 아주 잘 통합된다. 전형적인 스프링 어플리케이션에서, 많은 중요한 객체들은 데이터 접근 템플릿, (템플릿을 사용한)데이터 접근 객체들, 트랜잭션 매니저들, (데이터 접근 객체들과 트랜잭션 매니저들을 사용한)비지니스 객체들, 웹 view 처리단, (비지니스 객체들을 사용하는)웹 컨트롤러 등의 JavaBeans이다.

2. 어플리케이션 컨텍스트에서의 리소스 정의
#

어플리케이션 객체들을 하드코딩된 리소스 룩업과 묶는 작업을 피하기 위해, 스프링은 JDBC DataSource나 하이버네이트 SessionFactory와 같은 리소스들을 어플리케이션 컨텍스트의 빈객체로 정의한다. 리소스들에 접근할 필요가 있는 어플리케이션 객체들은 단지 빈 레퍼런스를 경유하여 이미 정의된 인스턴스들에 대한 참조를 받기만 하면 된다.(다음 섹션의 DAO 정의는 이러한 점을 설명해줄 것이다.) XML 어플리케이션 컨텍스트 정의로부터 발췌된 다음의 예시는 어떻게 JDBC DataSource와 하이버네이트 SessionFactory를 정의할 것인지를 설명해주고 있다.
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName">
    <value>jdbc/myds</value>
  </property>
</bean>

<bean id="mySessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="mappingResources">
    <list>
      <value>product.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
    </props>
  </property>
  <property name="dataSource">
    <ref bean="myDataSource"/>
  </property>
</bean>

...

JNDI-located DataSource를 Jakarta Commons DBCP BasicDataSource 와 같은 로컬 정의된 것으로 변환하는 것이 단지 설정의 문제에 지나지 않는 다는 점에 주목해야 한다.

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName">
    <value>org.hsqldb.jdbcDriver</value>
  </property>
  <property name="url">
    <value>jdbc:hsqldb:hsql://localhost:9001</value>
  </property>
  <property name="username">
    <value>sa</value>
  </property>
  <property name="password">
    <value></value>
  </property>
</bean>

여러분은 JNDI-located SessionFactory 역시 사용할 수 있지만, EJB 컨텍스트가 아닌 경우에는 일반적으로 불필요한 일이다.(이 부분에 대한 논점은 "컨테이너 리소스 대 로컬 리소스" 섹션을 참고하도록 하라.)

3. 제어의 역전 : Template와 Callback
#

템플릿화와 어떠한 임의의 DAO 혹은 비지니스 객체들의 일부가 될 수 있는 메써드를 구현하기 위한 기초적인 프로그래밍 모델은 다음와 같을 것이다. 둘러싼 객체(surrounding objects)를 구현하는 것에는 어떠한 제역조건도 존재하지 않는다. 그것은 단지 하이버네이트의 SessionFactory를 제공할 뿐이다. 어디에서건 후자의 (implemets한) 객체를 얻을 수 있지만, 간단한 setSessionFactory 빈 프라퍼티 setter를 경유하여 스프링 어플리케이션 컨텍스트로부터 빈 참조를 하는 것이 바람직할 것이다. 다음의 조각코드는 스프링 어플리케이션 컨텍스트에서의 DAO 정의를 보여주는데, 이 DAO는 위의 코드에서 정의한 SessionFactory를 참조하고 있으며, DAO 메써드 구현의 예시가 될 것이다.
<bean id="myProductDao" class="product.ProductDaoImpl">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

...

public class ProductDaoImpl implements ProductDao {

  private SessionFactory sessionFactory;

  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  public List loadProductsByCategory(final String category) {
    HibernateTemplate hibernateTemplate = new HibernateTemplate(this.sessionFactory);
    return (ListhibernateTemplate.execute(
        new HibernateCallback() {
          public Object doInHibernate(Session sessionthrows HibernateException {
            List result = session.find("from test.Product product where product.category=?", category, Hibernate.STRING);
            // do some further stuff with the result list
            return result;
          }
        });
  }
}

callback 구현은 어떠한 하이버네이트 데이터 접근에서도 매우 효과적으로 사용될 수 있다. HibernateTemplate는 어떤 경우에서건 Sesion을 열고 닫는 문제를 적절하게 관리해줄 것이며, 자동으로 트랜잭션 처리를 해줄 것이다. 템플릿 인스턴스들은 쓰레드 안전하며 재사용가능하다. 그래서 그것들은 둘러싼 클래스의 인스턴스 변수들로서 유지될 수 있다.
한 줄짜리 find, load, saveOrUpdate 혹은 delete 호출과 같은 간단한 동작들을 위해, HibernateTemplate는 대체사용할 수 있는 편리한 메써드들을 제공하는데, 이 메써드들은 한줄짜리 callback 구현들로 대체 가능하다. 더군다나, 스프링은 편리한 HibernateDaoSupport 기본 클래스를 제공하는데, 이것은 SessionFActory를 받기 위한 setSessionFactory 메써드와 하위 클래스들에 의해 사용가능한 getSessionFactory, getHibernateTemplate 메써드를 제공하고 있다. 이러한 조합은 일반적인 CRUD 요구를 처리하기 위한 DAO 구현체는 매우 간단하게 작성 가능하다.

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

  public List loadProductsByCategory(String category) {
    return getHibernateTemplate().find("from test.Product product where product.category=?", category);
  }
}


4. 템플릿 대신 AOP 인터셉터를 적용하기
#

HibernateTemplate를 사용을 대체할만한 것은 스프링의 AOP HibernateInterceptor이다. 이것은 callback 구현을 try/catch 블럭내에 위치한 직접적인 Hibernate code와, 어플리케이션 컨텍스트 내에 각자의 인터셉터 설정으로 대체한다. 다음의 조각코드는 스프링 어플리케이션 컨텍스트에서의 개별적인 DAO, 인터셉터 그리고 프락시 정의를 나타내고 있으며, DAO 메써드 구현의 예시가 될 수 있다.
...

<bean id="myHibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

<bean id="myProductDaoTarget" class="product.ProductDaoImpl">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

<bean id="myProductDao" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>product.ProductDao</value>
  </property>
  <property name="interceptorNames">
    <list>
      <value>myHibernateInterceptor</value>
      <value>myProductDaoTarget</value>
    </list>
  </property>
</bean>

...

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

  public List loadProductsByCategory(final String categorythrows MyException {
    Session session = SessionFactoryUtils.getSession(getSessionFactory()false);
    try {
      List result = session.find("from test.Product product where product.category=?"
                    category, Hibernate.STRING);
      if (result == null) {
        throw new MyException("invalid search result");
      }
      return result;
    }
    catch (HibernateException ex) {
      throw SessionFactoryUtils.convertHibernateAccessException(ex);
    }
  }
}

이 메써드는 단지 해당 메써드를 위해 만들어진, 메써드 호출 이전에 쓰레드 기반의 Session을 열고, 호출 이후에 닫는 것을 관리해주는 HibernateInterceptor에서만 동작할 것이다. getSession 메써드에 포함된 "false" 플래그는 Session이 반드시 이미 존재해야만 한다는 것을 보장하기 위한 것이다. 만약 존재하지 않는다면, SessionFactoryUtils는 새로운 세션을 생성할 것이다. 만약 이미 HibernateTransactionManager 트랜잭션에 의해 예를 들어 SessionHolder가 쓰레드에 존재한다면, SessionFactoryUtils는 어떤 경우에서라도 자동으로 그 일부분이 될 것이다. HibernateTemplate는 SessionFactoryUtils 아래에서 그것을 이용할 것이다. 이것은 모두 동일한 하부구조이다.
HibernateInterceptor의 주된 장점은 DAO 내부에서 던져진 어떠한 checked 어플리케이션 익셉션도 허용한다는 점이다. 반면에, HibernateTemplate는 callback 내부에서 unchecked 익셉션들에 제약된다. 사람은 종종 개별적인 체크와 어플리케이션 익셉션들을 callback 이후에 던지는 것을 유예할 수 있다는 점에 주의해야 한다. 인터셉터의 주된 결점은 그것이 컨텍스트 내부에 특별한 셋업을 필요로 한다는 점이다. HibernateTemplate의 간편한 메써드들은 많은 경우에서 더욱 간단한 해법을 제시해준다.

5. 프로그래밍적인 트랜잭션 구분
#

낮은 레벨의 데이터 접근 서비스들에 덧붙혀, 트랜잭션은 어플리케이션의 보다 높은 레벨에서 많은 수의 동작들에 걸쳐져 구분될 수 있다. 여기에서도 역시 둘러싼 비지니스 객체의 구현에는 아무런 제약이 없다. 단지 스프링 PlatformTransactionManager를 사용하기만 하면 된다. 다시, 후자는 어디에서건 가져올 수 있지만, setTransactionManager 메써드를 통해 빈 참조를 하는 것이 바람직하다. productDAO가 setProductDao 메써드를 통해 세팅되는 것처럼 말이다. 다음의 조각코드는 스프링 어플리케이션 컨텍스트 내의 트랜잭션 매니저와 비지니스 객체 정의이며, 비지니스 메써드 구현의 예시가 될 것이다.
...

<bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

<bean id="myProductService" class="product.ProductServiceImpl">
  <property name="transactionManager">
    <ref bean="myTransactionManager"/>
  </property>
  <property name="productDao">
    <ref bean="myProductDao"/>
  </property>
</bean>

public class ProductServiceImpl implements ProductService {

  private PlatformTransactionManager transactionManager;
  private ProductDao productDao;

  public void setTransactionManager(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }

  public void setProductDao(ProductDao productDao) {
    this.productDao = productDao;
  }

  public void increasePriceOfAllProductsInCategory(final String category) {
    TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
      public void doInTransactionWithoutResult(TransactionStatus status) {
        List productsToChange = productDAO.loadProductsByCategory(category);
        ...
      }
    });
  }
}

6. 트랜잭션 구분 선언하기
#

대안으로 스프링의 AOP TransactionInterceptor를 사용할 수 있는데, 이것은 트랜잭션 구분 코드를 어플리케이션 컨텍스트의 인터셉터 설정으로 대체하는 방식이다. 이 방법은 비지니스 객체들 각각의 비지니스 메써드 내의 반복적인 트랜잭션 구분코드를 제거해준다. 더군다나 전달행위(propagation behavior)와 격리수준(isolation level)과 같은 트랜잭션 문법들이 설정 파일에서 변경될 수 있으며, 비지니스 객체 구현에는 아무런 영향을 주지 않는다.
...

<bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

<bean id="myTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager">
    <ref bean="myTransactionManager"/>
  </property>
  <property name="transactionAttributeSource">
    <value>
      product.ProductService.increasePrice*=PROPAGATION_REQUIRED
      product.ProductService.someOtherBusinessMethod=PROPAGATION_MANDATORY
   </value>
  </property>
</bean>

<bean id="myProductServiceTarget" class="product.ProductServiceImpl">
  <property name="productDao">
    <ref bean="myProductDao"/>
  </property>
</bean>

<bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>product.ProductService</value>
  </property>
  <property name="interceptorNames">
    <value>myTransactionInterceptor,myProductServiceTarget</value>
  </property>
</bean>

public class ProductServiceImpl implements ProductService {

  private ProductDao productDao;

  public void setProductDao(ProductDao productDao) {
    this.productDao = productDao;
  }

  public void increasePriceOfAllProductsInCategory(final String category) {
    List productsToChange = this.productDAO.loadProductsByCategory(category);
    ...
  }
}

HibernateInterceptor와 마찬가지로, TransactionInterceptor 역시 어떠한 checked 어플리케이션 익셉션도 callback 코드와 함께 던져질 수 있으나, TransactionTemplate는 callback 내에서 unchecked 익셉션들에 의해 제약받는다. TransactionTemplate는 unchecked 어플리케이션 익셉션의 경우 혹은 만약 트랜잭션이 (TransactionStatus를 통하여) 어플리케이션에 의해 rollback-only 를 표시해왔다면, 롤백을 실행할 것이다. TransactionInterceptor는 기본적으로는 같은 방식으로 동작할테지만, 설정가능한 롤백 정책들을 개별 메써드마다 허용해준다.
명시적인 트랜잭션을 세팅해주는 편리한 대안은, 만약 다른 AOP 인터셉터들이 존재하지 않을 경우, TransactionProxyFactoryBean을 사용하는 것이다. TransactionProxyFactoryBean은 proxy 정의와 특정한 타겟 빈의 트랜잭션 설정을 묶어준다. 이것은 하나의 타겟 빈과 하나의 프락시 빈을 더하는 설정상의 수고를 덜어준다. 더군다나, 당신은 트랜잭션 처리된 메써드가 정의되어 있는 인터페이스 혹은 클래스가 무엇인지에 대해 기술할 필요도 없다.

...

<bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
  </property>
</bean>

<bean id="myProductServiceTarget" class="product.ProductServiceImpl">
  <property name="productDao">
    <ref bean="myProductDao"/>
  </property>
</bean>

<bean id="myProductService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref bean="myTransactionManager"/>
  </property>
  <property name="target">
    <ref bean="myProductServiceTarget"/>
  </property>
  <property name="transactionAttributes">
    <props>
      <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
      <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
    </props>
  </property>
</bean>

7. 트랜잭션 관리 전략
#

TransactionTemplate와 TransactionInterceptor는 실질적인 트랜잭션 관리를 PlatformTransactionManager 인스턴스에 전달한다. 여기에서의 PlatformTransactionManager 인스턴스는 하이버네이트 어플리케이션을 위해 사용되는 (ThreadLocal Session을 사용하는 단일한 하이버네이트 SessionFactory를 위한) HibernateTransactionManager가 될 수도 있고, (컨테이너의 하위 시스템에 JTA를 전달하는) JtaTransactionManager가 될 수도 있다. 당신은 또한 사용자 정의의 PlatformTransactionManager 구현체를 사용할 수도 있다.
때문에 어플리케이션 개발시 특정 배포판을 위해 분산 트랜잭션의 요구가 발생했을 경우, native 하이버네이트 트랜잭션 매니저를 JTA 등으로 변환하는 것은 설정상의 문제가 된다. 단순히 하이버네이트 트랜잭션 매니저를 스프링의 JTA 트랜잭션 매니저 구현체로 대체하면 된다. 트랜잭션 명시와 데이터 접근 코드는 모두 변경없이 작동될 것이고, 때문에 그것들은 단지 일반적인 트랜잭션 관리 API를 사용하기만 하면 된다.
다수의 하이버네이트 세션 팩토리들을 교차하는 분산 트랜잭션 환경을 위해서는, 단지 트랜잭션 전략으로서의 JtaTransactionManager를 여러 개의 LocalSessionFactoryBean 정의와 결합시켜주어야 한다. 그러면, 각각의 DAO들은 하나의 특정한 SessionFactory 참조를 가지고 그것을 각각의 빈 프라퍼티에 넘겨주면 된다. 만약 모든 기반 JDBC 데이터소스들이 트랜잭셔널한 컨테이너의 것들이라면, 비지니스 객체는, JtaTransactionManager를 트랜잭션 전략으로 사용하는 한, 어떠한 수의 DAO와 세션팩토리들을 교차하고 있는 트랜잭션도 특별한 주의 없이 구분지을 수 있다.
<bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName">
    <value>jdbc/myds1</value>
  </property>
</bean>

<bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName">
    <value>jdbc/myds2</value>
  </property>
</bean>

<bean id="mySessionFactory1" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="mappingResources">
    <list>
      <value>product.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
    </props>
  </property>
  <property name="dataSource">
    <ref bean="myDataSource1"/>
  </property>
</bean>

<bean id="mySessionFactory2" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="mappingResources">
    <list>
      <value>inventory.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">net.sf.hibernate.dialect.OracleDialect</prop>
    </props>
  </property>
  <property name="dataSource">
    <ref bean="myDataSource2"/>
  </property>
</bean>

<bean id="myTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

<bean id="myProductDao" class="product.ProductDaoImpl">
  <property name="sessionFactory">
    <ref bean="mySessionFactory1"/>
   </property>
</bean>

<bean id="myInventoryDao" class="product.InventoryDaoImpl">
  <property name="sessionFactory">
    <ref bean="mySessionFactory2"/>
  </property>
</bean>

<bean id="myProductServiceTarget" class="product.ProductServiceImpl">
  <property name="productDao">
    <ref bean="myProductDao"/>
  </property>
  <property name="inventoryDao">
    <ref bean="myInventoryDao"/>
   </property>
</bean>

<bean id="myProductService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref bean="myTransactionManager"/>
  </property>
  <property name="target">
    <ref bean="myProductServiceTarget"/>
  </property>
  <property name="transactionAttributes">
    <props>
      <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
      <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
    </props>
  </property>
</bean>

8. 스프링 관리되는 어플리케이션 빈즈 사용하기
#

스프링 어플리케이션 컨텍스트 정의는 다양한 컨텍스트 구현 클래스들레 의해 로드될 수 있는데, FileSystemXmlApplicationContext, ClassPathXmlApplicationContext, XmlWebApplicationContext 등이 그것이다. 이것들은 스프링 관리되는 DAO와 비즈니스 객체들을 모든 종류의 환경에서 재사용 가능하게 해준다. 디폴트로, 웹 어플리케이션은 root 컨텍스트가 "WEB-INF/applicationContext.xml" 로 설정되어 있다.
어떠한 스프링 어플리케이션에서도, 어플리케이션 컨텍스트는 XML 파일로 정의되는데, 이것은 모든 연관된 어플리케이션 빈들, 하이버네이트 세션 팩토리에서부터 (위에서 본 빈들과 같은) 사용자 정의 DAO와 비지니스 객체들까지 묶어준다. 그것들 대부분은 스프링 컨테이너에 의해 관리되고 있다는 사실을 알 필요가 없으며, JavaBeans 협약에 따르기만 한다면, 심지어 다른 빈들과 공동으로 사용될 때도 마찬가지다. 빈 프라퍼티는 값 파라미터를 나타낼 수도 있고 연관된 다른 빈을 나타낼 수도 있다. 다음의 빈 정의는 루트 어플리케이션 컨텍스트에서 비지니스 빈들에 접근하는 스프링 web MVC 컨텍스트의 일부분이 될 수 있다.
<bean id="myProductList" class="product.ProductListController">
  <property name="productService">
    <ref bean="myProductService"/>
  </property>
</bean>

스프링 웹 컨트롤러는 필요한 모든 비지니스 객체 혹은 DAO를 빈 참조를 통해 제공받는데, 때문에 일반적으로 어플리케이션 컨텍스트에서 수동적인 빈 룩업을 해 줄 필요가 없다. 그러나, 스프링 관리되는 빈들을 Struts와 함께 사용하거나 EJB 구현클래스 혹은 애플릿 내에서 사용하고자 한다면, 항상 수동으로 빈을 룩업해주어야 한다. 그러나, 스프링 빈은 사실상 어디에서건 (적은 노력으로 큰 성과를 내는) 지렛대와 같은 역할을 해준다. 어플리케이션 컨텍스트에 대한 참조값을 얻고자 한다면, 웹의 경우 서블릿컨 텍스트를 통해서 가능하고, 혹은 파일이나 클래스패쓰 리소스로부터 인스턴스를 수동으로 생성하면 된다.

ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
ProductService productService = (ProductServicecontext.getBean("myProductService");

ApplicationContext context = new FileSystemXmlApplicationContext("C:/myContext.xml");
ProductService productService = (ProductServicecontext.getBean("myProductService");

ApplicationContext context = new ClassPathXmlApplicationContext("myContext.xml");
ProductService productService = (ProductServicecontext.getBean("myProductService");

9. 컨테이너 리소스 대 로컬 리소스
#

스프링의 리소스 관리는 어플리케이션 코드의 단 한줄도 바꾸지 않은 채, JNDI SessionFactory와 로컬 사이의 변환을 간단하게 해준다. JNDI DataSource의 경우도 마찬가지이다. 리소스 정의를 컨테이너에 유지할 것인가 혹은 어플리케이션 내에 로컬로 유지할 것인가는 주되게는 사용될 트랜잭션 전략의 문제이다. 스프링 정의된 로컬 SessionFactory에 비교하자면, 수동으로 등록된 JNDI SessionFactory는 어떠한 장점도 제공해주지 않는다. 만약 하이버네이트의 JCA connector를 통해 등록된다면, 명백하게 JTA 트랜잭션의 일부가 된다는 부가적인 가치가 있는데, 이것은 특히 EJB 내부에서 더욱 그러하다.
스프링의 트랜잭션 지원의 중요한 장점은 어떠한 컨테이너에도 종속되지 않는다는 점이다. JTA가 아닌 다른 어떤 전략을 설정했을 지라도, 독립적 혹은 테스트 환경에서도 역시 작동할 것이다. 특히, 전형적인 단일 데이터베이스 트랜잭션의 경우, 이것은 매우 경량이면서도 강력한 JTA에 대한 대안이 될 것이다. 트랜잭션을 관리하기 위해 로컬 EJB 비상태 세션빈을 사용할 때, 당신은 EJB 컨테이너와 JTA 모두에 의존하게 된다. 심지어 만약 당신이 단지 단일 데이터베이스에 접근해서 단지 CMT를 통한 트랜잭션 기술을 위해 비상태 세션빈을 사용한다고 해도 마찬가지다. JTA 프로그래밍적인 방법으로 JTA를 사용하는 것을 대체하는 것 또한 J2EE 환경을 요구된다.
JTA는 단지 JTA 자체와 JNDI DataSource의 관점에서 컨테이너 의존성에만 관련되는 것은 아니다. 스프링을 사용하지 않은 JTA 관리 하이버네이트 트랜잭션을 사용할 때, 적절한 JVM 차원의 캐슁을 위해서 하이버네이트 JCA connector를 사용하거나 설정된 JTATransaction을 가지고 별도의 하이버네이트 트랜잭션 코드를 작성해야 한다. 스프링 관리 트랜잭션은 로컬로 정의된 하이버네이트 SessionFactory로 매우 잘 작동하며, 만약 물론 단일 데이터베이스에 접근하는 것이라면, 로컬 JDBC DataSource와도 마찬가지로 잘 동작한다. 또한 당신은 실제로 분산 트랜잭션의 요구에 직면하게 되었을 때에는 스프링의 JTA 트랜잭션으로 단지 되돌리기만 하면 된다.
JCA connector는 컨테이너 특유의 배포 단계와 처음부터 JCA 지원을 명백하게 요구한다. 이것은 로컬 리소스 정의와 스프링 관리 트랜잭션으로 개발된 간단한 웹 어플리케이션을 배포하는 것보다 분명 힘든 일일 것이다. 그리고 당신은 종종 컨테이너의 Enterprise Edition을 필요로 할텐데 예를 들어, WebLogic Express는 JCA를 제공해주지 않는다. 로컬 리소스들과 하나의 단일 데이터베이스에 걸쳐진 트랜잭션을 구현한 스프링 어플리케이션은 톰캣, 레진, 혹은 간단한 제티와 같은 어떠한 J2EE 웹 컨테이너(JTA, JCA 혹은 EJB 없이)에 잘 작동할 것이다. 부가적으로, 그 미들티어는 데스크탑 어플리케이션 혹은 테스트슈트에서도 쉽게 재사용 가능하다.
아무리 생각해봐도, 만약 당신이 EJB를 사용할 필요가 없다면, 로컬 SessionFactory 셋업과 스프링의 HibernateTransactionManager 혹은 JtaTransactionManager를 결합하여 사용하는 것이 좋다. 당신은 적절한 트랜잭션 가능한 JVM 차원의 캐슁과, 특정 컨테이너 배포판의 어려움이 없는, 분산 트랜잭션 지원 등의 모든 장점을 취할 수 있다. EJB 내에서 사용할 경우, JCA connector를 통한 하이버네이트 SessionFactory의 JNDI 등록만이 사용할 가치가 있을 것이다.

10. 스켈레톤과 예제
#

스프링과 하이버네이트를 사용한 J2EE 어플리케이션의 보다 자세한 예제를 원한다면, 스프링 프레임워크 배포판에 들어 있는 "웹어플리케이션-전형적인" 스켈레톤을 살펴보는 것이 좋다. 이것은 다양한 데이터 소스와 JDBC와 하이버네이트 어플리케이션에 적절한 트랜잭션 매니저 설정 옵션들을 개괄하고 있다. 이것은 또한 트랜잭션 인터셉터에 주목하여 어떻게 AOP 인터셉터들을 설정하는지를 보여준다.
1.0 M2 릴리즈에 따라, 스프링 배포판의 Petclinic 예제는 JDBC와 하이버네이트를 위한 대안적인 DAO 구현과 어플리케이션 설정들을 제공한다. 그렇기 때문에, Petclinic은 스프링 웹 어플리케이션 내에서 하이버네이트를 사용을 설명해주는 작동되는 예제 어플리케이션으로 제공된다. 이것은 또한 다른 트랜잭션 전략들에 따라 트랜잭션 구분을 명시하는 방법을 쉽게 도와줄 것이다.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
txt
1-1.txt 1.2 kB 1 06-Apr-2006 09:45 p48145-adsau12honb2-acca.tokyo.ocn.ne.jp
doc
AlgorismLwy.doc 288.8 kB 1 06-Apr-2006 09:45 p48145-adsau12honb2-acca.tokyo.ocn.ne.jp
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor