Whiteship's Note


하이버네이트, 스프링, 트랜잭션, OSIV(Open Session In View) 패턴

Spring/Chapter 12 : 2008.11.17 15:10


참조

No Hibernate Session bound to thread 에러로 시작한 OSIV 얘기
http://forum.springframework.org/archive/index.php/t-33082.html

논쟁에서 언급한 HibernateTemplate API
http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/HibernateTemplate.html

스프링의 OpenSessionInViewFilter API
http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html

하이버네이트 위키 OSIV
http://www.hibernate.org/43.html

손권남님의 OSIV 정리
http://kwon37xi.springnote.com/pages/1075048

InfoQ에서 Spring In Production 요약한 것
http://www.infoq.com/news/2007/11/spring-production

댓글에 보면 OSIV 패턴이 안티 패턴인가에 대한 내용이 있음
http://blog.springsource.com/2007/11/09/download-the-spring-in-production-white-paper/

Tip4에서 OSIV 패턴은 안티 패턴이라고 한다.
http://www.realsolve.co.uk/site/tech/orm-performance.php

이미 세션이 닫힌 상태에서 뷰에서 Detached 객체에서 아직 로딩하지 않은 객체에 접근하면 에러(LazyInitializationException)가 발생합니다. 이 에러는 정상적인 에러죠. 하이버네이트 맵핑을 Lazy loading으로 최소한의 객체만 로딩하도록 하고 필요할 때 연관을 맺고 있는 객체를 추가로 가져오도록할 때 흔히 발생하는 아주 정상적인 에러입니다.

이런 상황을 극복하려면 필요한 객체를 미리 팻치(Eager Fatch)해둔 상태로 로딩하는 메소드를 DAO쪽에 구현해서 뷰에서 추가로 세션을 사용할 필요없이 뷰 랜더링을 마치도록 하면 되기도 하고..

아니면 OSIV 패턴을 사용해서, 인터셉터나 필터를 적용해서 뷰를 완전히 랜더링 할 때까지 세션을 열어 두는 겁니다. 그럼 뷰를 랜더링 하다가 필요한 객체가 있으면 세션을 사용해서 로딩할 수 있겠죠. 그럼 위의 방법처럼 DAO에 이른 팻치를 해서 가져오는 별도의 메소드나 쿼리를 만들지 않아도 되겠죠.

흠.. 하지만 뷰에서 자신도 예측하지 못한 쿼리가 계속 발생할 여지도 있고, 웹 단에서 데이터 캡슐화를 깨기 때문에 OSIV를 안티패턴으로 보고 사용을 최소화 해야 한다는 주장도 있습니다.

스프링 + 하이버네이트 조합을 쓸 때 OpenSessionInViewFilter를 자주 사용하는데, 이 API는 아주 잘 봐둘 필요가 있습니다. 그 중에서 가장 주목해야 할 부분은 다음과 같습니다.

1. 기본 플러시 모드는 FlushMode.NEVER입니다. NEVER는 deprecated 됐는데, 아직 사용하고 있습니다. 이 걸 왜 주의해야 하냐면, OSIV를 등록하고 만약 서비스 계층에 @Trasaction을 설정하지 않았다고 해봅시다. 그럴 때 트랜잭션 처리는 됩니다. 왜냐면 OSIV 때문이죠. 요청이 들어오면 해당 요청을 처리할 쓰레드에 새로운 세션 만들고 그걸로 트랜잭션 만들어서 쓰기 때문에 트랜잭션은 있는데 문제는 플러시 모드가 NEVER라서 명시적으로 flush()를 호출하기 전까지는 절대로 DB에 반영이 안 됩니다.

2. Conversation을 하나의 세션을 늘리는 방법(extending a sesion for conversation)으로 구현할 경우에, persistence 객체의 reassociation을 최대한 요청 처리 초기에 해야 합니다. 안 그럼 나중에 동일한 객체가 또 있다고 충돌 날 수가 있습니다. 이 문제에 대응하기 위해서 singleSession이라는 옵션에 false 값을 줄 수도 있지만, 그렇게 하면 매 요청 마다 새로운 Session 만들어서 사용하겠다는 것이고 Conversation을 구현하는 다른 방법(detached 객체를 가지고 구현하는 방법)으로 구현해야 될 것 같습니다. 즉 코딩할 때 조금 주의해서 단일 세션으로 Conversation을 사용할 것이냐, 아님 단순하게 요청 마다 새로운 세션으로 만들고 객체들을 붙였다 띄었다 할 것이냐 인데.. 필요한 객체를 계속 하나의 세션에 들고 유지하는게 좀 더 효율적이지 않을까 싶네요.


top


@Repository를 쓴다면 하이버네이트 예외 변환기 직접 만들 필요 없습니다.

Spring/Chapter 12 : 2008.11.12 10:52


스프링이 2.0부터 제공하고 있었는데, 이제서야 알게 됐습니다. 저는 그동안 뭘...;;;; 한 거죠.. ㅋㅋㅋ 이 것 참..;; 혹시 저만 빼고 다들 알고 계셨던건 아니겠죠? 저는 게다가 항상 @Repository 애노테이션을 쓰고 있었거든요. 그런데도 몰랐습니다. @Repository 애노테이션 API에도 안 나와있네요.

하이버네이트 예외 변환기가 언제 필요하냐면.
1. 하이버네이트 DAO 구현을 스프링 API에서 독립적으로 구현하고 싶을 때. 다른 말로 하이버네이트 SessionFacotry를 직접 주입해서 사용하고 싶을 때.
2. 하이버네이트 예외를 스프링 DataAccessException으로 변환하고 싶을 때.

위 두 가지 조건이 만족한다면 사용하고 싶을 겁니다. 그럴 때 만약 @Reposity라는 애노테이션으로 빈 스캔을 사용해서 빈을 등록하고 있다면 예외 처리기를 만들 필요 없이 빈 하나만 등록해주면 끝납니다.

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

끝~ 저 클래스는 이름이 암시하듯이 스프링의 빈 포스트 프로세서로, 등록된 빈 중에 @Repository가 붙어있는 빈에 persistenceExceptionTranslationAdvisor 이런 어드바이저를 적용한 프록시 빈으로 교체해주는 멋진 녀석입니다. AOP와 빈 포스프 프로세서의 조합. 캬오... 스프링엔 이렇게 멋진 코드가 곳곳에 숨어있군요.

핵심코드 감상 하기...
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass =
                (bean instanceof Advised ? ((Advised) bean).getTargetSource().getTargetClass() : bean.getClass());
        if (targetClass == null) {
            // Can't do much here
            return bean;
        }
       
        if (AopUtils.canApply(this.persistenceExceptionTranslationAdvisor, targetClass)) {
            if (bean instanceof Advised) {
                ((Advised) bean).addAdvisor(this.persistenceExceptionTranslationAdvisor);
                return bean;
            }
            else {
                ProxyFactory proxyFactory = new ProxyFactory(bean);
                // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
                proxyFactory.copyFrom(this);
                proxyFactory.addAdvisor(this.persistenceExceptionTranslationAdvisor);
                return proxyFactory.getProxy(this.beanClassLoader);
            }
        }
        else {
            // This is not a repository.
            return bean;
        }
    }

위 코드는 유겐 휄러와 로드 좐슨이 작성한 코드입니다.

ProxyFactory를 사용해서 직접 프록시 만드는 방법은 어제 올린 스크린 캐스팅에 포함되어 있었죠. Advised 인터페이스에 대해서도 언급 했었구요. 스프링 AOP 스캐를 보신 분들이라면 무리 없이 이해하실 수 있을 겁니다. 귿~
top


12.2.8. Transaction management strategies

Spring/Chapter 12 : 2007.04.27 09:26


TransactionTemplate 나 TransactionInterceptor 를 사용하든 둘 다 PlatformTransactionManager 객체를 사용하고 있습니다.
Hibernate를 위해 사용할 수 있는 TransactionManager로는 두 개가 있습니다.

1. HibernateTransactionManager :: for a single Hibernate SessionFactory, using a ThreadLocal  Session under the hood
2. JtaTransactionManager :: delegating to the JTA subsystem of the container
물론 직접 PlatformTransactionManager 인터페이스를 구현해서 사용해도 됩니다.

따라서 단일 트랜잭션 리소스를 사용하다가 분산된 트랜잭션 처리가 필요할 때는 간단히 JtaTransactionManager를 사용하도록 수정하면 됩니다.

아래의 코드는 여러개의 LocalSessionFactoryBean 를 사용하여 Oracle과 MySQL JDBC를 사용하는 JtaTransactionManager 설정을 보여줍니다.

<beans>

  <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName value="java:comp/env/jdbc/myds1"/>
  </bean>

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

  <bean id="mySessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource1"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.MySQLDialect
        hibernate.show_sql=true
      </value>
    </property>
  </bean>

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

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

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

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

  <!-- this shows the Spring 1.x style of declarative transaction configuration -->
  <!-- it is totally supported, 100% legal in Spring 2.x, but see also above for the sleeker, Spring 2.0 style -->
  <bean id="myProductService"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="myTxManager"/>
    <property name="target">
      <bean class="product.ProductServiceImpl">
        <property name="productDao" ref="myProductDao"/>
        <property name="inventoryDao" ref="myInventoryDao"/>
      </bean>
    </property>
    <property name="transactionAttributes">
      <props>
        <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
        <prop key="someOtherBusinessMethod">PROPAGATION_REQUIRES_NEW</prop>
        <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
      </props>
    </property>
  </bean>

</beans>

JTA가 JNDI에 종속적이기 때문에 JNDI를 사용하여 datasource를 가져오는 모습입니다. 단일 리소스를 사용할 경우에는 HibernateTracsactionManager를 사용하는 것이 좋으며 이 때는 datasource를 가져 올때 걍 평범하게 가져오면 됩니다.


top


12.2.7. Declarative transaction demarcation

Spring/Chapter 12 : 2007.04.25 13:00


이번엔 선언적인 트랜잭션 경계 설정에 관해 살펴보겠습니다. Spring AOP를 사용하는 방법으로 이전에는 Service Layer에 트랜잭션과 관련된 코드가 들어있었다면 Service Layer이 오직 자신의 본연의 임무에만 충실할 수 있도록 코드가 다음과 같이 바뛰게 됩니다.
public class ProductServiceImpl implements ProductService {

private ProductDao productDao;

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

// notice the absence of transaction demarcation code in this method
// Spring's declarative transaction infrastructure will be demarcating transactions on your behalf
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
}
이전 글에서의 빨간색 글자 부분이 모두 사라졌으며 TransactionTemplate 변수 역시 필요없게 되었습니다. 이 말은 Spring 관련 API가 코드에서 사라졌다는 것이죠. 다시 또 말하면  Spring의 non-invasive 특징을 잘 나타내주고 있습니다.

Spring 1.2 방식의 AOP를 사용하여 트랜잭션 경계를 설정하는 방법은 다음과 같습니다.
<beans>

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

<bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="product.ProductService"/>
<property name="target">
<bean class="product.DefaultProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myTxInterceptor</value> <!-- the transaction interceptor (configured elsewhere) -->
</list>
</property>
</bean>

</beans>
TransactionInterceptor 와 TransactionTemplate의 차이
* 발생 시키는 Exception 유형 :: Interceptor는 throwable 타입, Template은 transactionException 타입(Runtime Exception)
* 롤백 설정 단위 :: Interception 는 메소드마다 정책을 다르게 설정해 줄 수 있다. Template은 어플리케이션에서 사용하는 TransactionStatus 객체에 설정하기 땜시 단위가 다름.

위의 설정 내용을 Spring 2.0 방식으로 다음과 같이 간단하게 바꿀 수 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<!-- SessionFactory, DataSource, etc. omitted -->

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

<aop:config>
<aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
</aop:config>

<tx:advice id="txAdvice" transaction-manager="myTxManager">
<tx:attributes>
<tx:method name="increasePrice*" propagation="REQUIRED"/>
<tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>

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

</beans>

top


12.2.6. Programmatic transaction demarcation

Spring/Chapter 12 : 2007.04.25 12:41


프로그래밍적인 방법으로 트랜잭션 경계를 지정하는 방법은 다음과 같이 비즈니스 로직 부분에 PlatformTransactionManager 를 인스턴스로 등록하고 Setter Injection을 사용하는 것입니다.
<beans>

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

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

</beans>
아래의 코드를 통해 위 설정 내용이 어떻게 적용 되는지 확인할 수 있습니다.
public class ProductServiceImpl implements ProductService {

private TransactionTemplate transactionTemplate;
private ProductDao productDao;

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

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

public void increasePriceOfAllProductsInCategory(final String category) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {

public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// do the price increase...
}
}
);
}
}
TransactionTemplate 객체를 사용하여 콜백과 템플릿을 사용하여 원하는 코드를 트랜잭션 처리 하에 처리하게 됩니다.

단점은 트랜잭션 처리할 부분에 빨간색의 코드들이 추가적으로 중복되게 됩니다. 이런 중복은 AOP를 사용하여 따로 빼낼 수 있는데요. 그런 방법을 다음 챕터인 선언적인 트랜잭션 경계 선언에서 알아보겠습니다.
top


12.2.5. Implementing DAOs based on plain Hibernate3 API

Spring/Chapter 12 : 2007.04.25 12:25


Hibernate 3.0.1 에서는 "Contexture Sessions"라고 부르는, 트랜잭션 당 하나의 current 세션을 사용하는 방법이 있습니다.
public class ProductDaoImpl implements ProductDao {

private SessionFactory sessionFactory;

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

public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
Spring 에서도 위와 같은 방법으로 코딩을 하면 트랜잭션 당 하나의 Hibernate Session객체를 사용하여 작업을 합니다.

위의 소스코드는 다음과 같은 DI가 필요합니다.
<beans>

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

</beans>
이런 스타일의 코딩의 장점은 Spring API에 종속 되지 않는 다는 것입니다. 오직 Hibernate API만 사용하고 있습니다.

단점은 HibernateException을 발생시키는데 이 것으로는 optimistic locking 이 실패 했다거나 하는 것을 알려주지 못합니다.

결론은 DAO를 구현할 때 Spring을 사용하여(HibernateTransactionManager) 트랜잭션 관리도 하고 DataAccessException을 사용할 수도 있지만 Hibernate3 API만 사용하여 구현할 수도 있습니다.
top


레퍼런스 12장에 오타

Spring/Chapter 12 : 2007.04.24 13:14


문제가 발생한 코드는 Spring Reference에 있던 소스 코드입니다.
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

public Collection loadProductsByCategory(String category)
throws DataAccessException, MyException {

Session session = 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 convertHibernateAccessException(ex);
}
}
}
위 코드에 잘못된 부분이 getSession 메소드 뿐이 아니였습니다. 바로 아래에 있는 session.find()도 문제가 있습니다. find() 메소드는 Hibernate의 org.hibernate.classic 패키지의 Session 인터페이스에 있습니다. 그러나 보통 사용하는 Session은 org.hibernate 패키지의 Session 인터페이스이였습니다. 그리고 그곳에는 find()라는 메소드가 없지요.

classic 패키지에 있는 Session이 상위 패키지에 있는 Session 인터페이스를 상속하고 있습니다. 하지만.. 모든 인터페이스가 deprecated 됐습니다. -_-;;
사용자 삽입 이미지

굳이 deprecated된 인터페이스를 사용(org.hibernate.classic.Sessoin 사용)하는 예제코드의 의도를 살리려면 다음과 같이 코드가 바껴야 합니다.

import org.hibernate.classic.Session;

        Session session = (Session) getSession(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;
        }

org.hibernate.Session을 사용하려면 다음과 같이 코드를 바꿔야 합니다.

        Session session = getSession(false);
        try {
            List result = session.createQuery("from test.Product product where product.category=?").setString(0, category).list();
            if (result == null) {
                throw new MyException("invalid search result");
            }
            return result;
        }

구형 인터페이스를 사용하고 캐스팅을 사용하느냐(처음 것) 아니면 신형 인터페이스를 사용하여 캐스팅도 없애느냐(뒤에 있는 것). 의 문제 인데요. 당연히 후자가 되어야 적당하다고 생각합니다.

전자에서 사용한 인터페이스는 Hibernate2 부터 deprecated 된거라고 신형 Session을 사용하라고 API에도 나와있습니다.
An extension of the Session API, including all deprecated methods from Hibernate2. This interface is provided to allow easier migration of existing applications. New code should use org.hibernate.Session.
용겐 횔러 횽하 신형 API 사용해서 예제 수정 해주세욜~
top


12.2.4. Implementing Spring-based DAOs without callbacks

Spring/Chapter 12 : 2007.04.24 11:36


콜백 메소드를 사용하지 않고 HibernateDaoSupport로 부터 Session을 구해서 다음과 같이 코딩할 수 있습니다.

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public Collection loadProductsByCategory(String category)
            throws DataAccessException, MyException {

        Session session = 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 convertHibernateAccessException(ex);
        }
    }
}

이 때 유용하게 사용할 수 있는 메소드로는 역시 Session을 가져다 주는 getSession메소드 입니다.

희한하네요. getSession() 메소드의 인자가 두 개인 녀석이 안보이는데 레퍼런스에 있는 예제 코드가 잘못된 것 같습니다. 이 것도 이슈트랙에 올려야 겠군요. "용겐 휄러 횽하 예제 코드가 또 틀렸어효"

getSession(); getSession(boolean allowCreate); => HibernateDaoSupport 클래스에 있고
getSession(SessionFactory sessionFactory, boolean allowCreate); => SessionFactoryUtils 클래스에 있습니다.

어쨋든 allowCreate 속성은 Session을 만들 때 가져올 때 현재 트랜잭션에서 가져오느냐 아니면 새 트랜잭션에서 가져오느냐를 설정해 줍니다. true면 새거 false면 현재 트랜잭션을 사용하게 됩니다.

convertHibernateAccessException(ex); 메소드는 이름에서도 알 수 있듯이 Hibernate에서 발생하는 예외를 바꿔줍니다. unchecked Exception으로 바꿔주는 일을 하겠죠.

즉 하드코딩을 하더라도 Spring이 제공하는 예외 랩핑을 사용할 수 있지만 try-catch 보기도 싫고 굳이 예외 변경하는 코드를 매번 넣어야 하기 때문에 대부분의 경우 HibernateTemplate을 사용하는게 편할 것 같습니다.


top


12.2.3. The HibernateTemplate

Spring/Chapter 12 : 2007.04.23 19:04


이 전 글에서 Spring Container에 등록한 SessionFactoryBean을 사용하여 HibernateTemplate을 생성할 수 있습니다.

따라서 HibernateTemplate을 사용할 DAO 클래스에 setter injection을 사용하기 위해 보통 다음과 같이 설정합니다.
<beans>

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

</beans>

어플리케이션에서 사용할 때는 다음과 같이 콜백을 사용하여 session에 접근합니다.
public class ProductDaoImpl implements ProductDao {

    private HibernateTemplate hibernateTemplate;

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

    public Collection loadProductsByCategory(final String category) throws DataAccessException {
        return this.hibernateTemplate.execute(new HibernateCallback() {
            public Object doInHibernate(Session session) {
                Criteria criteria = session.createCriteria(Product.class);
                criteria.add(Expression.eq("category", category));
                criteria.setMaxResults(6);
                return criteria.list();
            }
        };
    }
}

HibernateTemplate이 Session의 생성이나 소멸, 트랜잭션을 책임집니다. 따라서 콜백을 사용하여 단순하게 할 일(find, select, insert 등) 만 코딩해 주면됩니다.

HibernateTemplate의 execute() 메소드 소스 코드를 보시면 어떤 일을 해주는지 짐작할 수 있습니다.

more..


한 단계 더 나가서 HibernateTemplate을 DAO 클래스 내부에 직접 멤버 변수로 선언하지 않고 DAO 클래스가 HibernateDaoSupport 클래스를 상속 받도록 하면 getHibernateTemplate() 메소드르 사용하여 원할 때 마다 HibernateTemplate을 사용할 수 있습니다.

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

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

이런식으로 사용하면 DAO마다 setter injection 해야하는 수고를 덜 수 있겠습니다.
top


12.2.2. SessionFactory setup in a Spring container

Spring/Chapter 12 : 2007.04.23 10:56


Spring 의 Application Context에 SessionFactory를 bean으로 등록해 둡니다.

다음은 Reference에 있는 XML 기반의 Hibernate 설정 파일을 사용할 때 등록하는 방법입니다.
<beans>

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

  <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.HSQLDialect
      </value>
    </property>
  </bean>

</beans>

다음은 찬욱이가 만들어 둔 어노테이션을 사용하는 Hibernate 설정을 사용했을 때의 SessionFactoryBean을 등록하는 예입니다. dataSource는 어딘가 다른 곳에 있습니다.
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="annotatedClasses">
            <list>
                <value>buyingBook.model.Member</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <!-- 지금 사용하고 있는 데이터베에스에 최적화 된 SQL을 생성하기 위해서
                      현재 데이터베이스에 해당하는 Hibernate dialect 클래스 명 -->
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>

                <!--  콘솔 창에 사용하는 모든 SQL문을 출력할지를 선택  -->
                <prop key="hibernate.show_sql">true</prop>

                <!-- DB Schema에 변경사항이 발생한 경우 자동으로 수정  -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>

                <!-- 성능 튜닝에 도움이 되는 유용한 통계를 제공  -->
                <prop key=" hibernate.generate_statistics">true</prop>

                <!-- 자동 커밋이 되는 것을 방지 -->
                <prop key="hibernate.connection.autocommit">false</prop>

                <!-- "Current" Session의 Scope 설정.<jta/thread/managed/custom> -->
                <prop key="hibernate.current_session_context_class">thread</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

어플리케이션에서 "sessionFactory"로 빈을 룩업하면 SessionFactory type의 객체를 반환합니다. 빨간색 클래스들의 상위 클래스인 AbstractSessionFactoryBean 클래스의 코드 일부를 참조하면 알 수 있습니다.
사용자 삽입 이미지

만약에 사용하는 ORM이 바뀌면 여기서 설정파일만 바꿔주면 됩니다. 예를 들어 JNDI에 있는 DataSource를 사용할 거라면 다음과 같이 바꿔줍니다.
<beans>

  <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myds"/>
  </bean>

</beans>

top


12.2.1. Resource management

Spring/Chapter 12 : 2007.04.23 00:46


자원을 관리하는 코드가 보통 사방에 흩어져 있게 됩니다. 하지만 스프링에서는 이런 자원 관리간단하면서도 강력한 방법인 tamplate을 사용한 IoC를 이용해서 관리합니다. 자원을 관리하는 기능과 SQLException을 보다 구체적이고 un-checked Exception인 DataAccessException으로 래핑해줍니다.

HibernateTemplate, HibernateInterceptor, HibernateTransactionManager 이런 클래스들을 제공하는 가장 주요한 목적
1. 어플리케이션에서 Data Access와 Transaction 기술을 깔끔하게 레이어링
2. 어플리케이션 객체들 간의 Loose Coupling

따라서...
1. no more business service dependencies on the data access or transaction strategy
2. no more hard-coded resource lookups
3. no more hard-to-replace singletons
4. no more custom service registries.


top


12.2. Hibernate

Spring/Chapter 12 : 2007.04.22 00:11


ORM 중에 Hibernate와 연동하는 방법을 살펴보겠습니다. 예제들은 Hibernate 3.0을 사용하였습니다.

12.2.1. Resource management

12.2.2. SessionFactory setup in a Spring container

12.2.3. The HibernateTemplate

12.2.4. Implementing Spring-based DAOs without callbacks

12.2.5. Implementing DAOs based on plain Hibernate3 API

12.2.6. Programmatic transaction demarcation

12.2.7. Declarative transaction demarcation

12.2.8. Transaction management strategies

12.2.9. Container resources versus local resources

12.2.10. Spurious application server warnings when using Hibernate


top


12.1. Introduction

Spring/Chapter 12 : 2007.04.21 23:54


 ORM DAO를 만들 때 Spring Framwork를 사용하면...

- 테스트 하기 편합니다.

Spring IoC 컨테이너를 사용할 수 있기 때문에 퍼시스턴스 관련 코드를 고립된(isolated) 상태로 테스트를 하는 것이 훨씬 용이합니다.

-Data Access Exception을 제공합니다.

ORM에서 발생하는 checked 예외를 감싸서 unchecked 예외인 DataAccessException 을 발생 시킵니다. 예외 처리하고 싶으면 해도 되고 안 해도 되기 때문에 try/catch 문을 제거 할 수 있습니다.

-일관된 자원 관리를 할 수 있습니다.

Spring은 Hibernate를 위한 SessionFactory, JDBC를 위한 DataSource, iBatis SQL Maps 설정과 속성을 위한 자원(resource)를 관리할 수 있습니다. 따라서 이들의 관리와 변경이 용이합니다.

-통합된 트랜잭션 관리를 할 수 있습니다.

O/R mapping 코드를 선언적인 방법 또는 프로그래밍 적인 방법의 트랜잭션 관리로 감쌀 수 있습니다. 두 방법 중 어떤 걸 사용하든 모두 원하는 대로 트랜잭션을 설정할 수 있으며 예외가 발생했을 때 원하는데로 처리(ex. 롤백) 할 수 있습니다. 또한 Hibernate나 JDO 관련 코드의 변경 없이 트랜잭션 관리자만 바꿀 수 있습니다.

이것에 대한 예제는 Spring 소스코드를 받으면 딸려 오는 sample 코드 중에 jpetstore 와 petclinic에 보시면 나와있습니다.

사용자 삽입 이미지

사용자 삽입 이미지

top


Chapter 12. Object Relational Mapping (ORM) data access

Spring/Chapter 12 : 2007.04.19 21:39


12.1. Introduction

Hibernate, JDO, Oracle TopLink, iBATIS SQL Maps, JPA 같은 ORM 과 Spring을 통합하여 Spring 의 Transaction 관리과 Exception 정책을 사용할 수 있습니다.

통합 하는 방법은 두 가지
1. using Spring's DAO 'templates'.
2. coding DAOs against plain Hibernate/JDO/TopLink/etc APIs.

12.2. Hibernate

Hibernate 와 연동하는 방법을 설명합니다. 주로 Hibernate 3.0 중심으로 설명 합니다.

12.3. JDO

Spring supports the standard JDO 1.0/2.0 API as data access strategy

12.4. Oracle TopLink

Since Spring 1.2, Spring supports Oracle TopLink (http://www.oracle.com/technology/products/ias/toplink) as data access strategy

TopLink 9.0.4, 10.1.3

12.5. iBATIS SQL Maps

The iBATIS support in the Spring Framework much resembles the JDBC / Hibernate support in that it supports the same template style programming and just as with JDBC or Hibernate, the iBATIS support works with Spring's exception hierarchy and let's you enjoy the all IoC features Spring has.

12.6. JPA

Spring JPA offers comprehensive support for the Java Persistence API in a similar manner to the integration with Hibernate or JDO

12.7. Transaction Management

선언적인 트랜잭션 관리를 위한 설정 내용 입니다.

more..

위 예제는 JpaTransactionManager를 사용한 경우입니다. Hibernate, Toplink, OpenJPA 를 위한 클래스도 제공합니다.

12.8. JpaDialect

Dialect 클래스를 사용함으로써 얻을 수 있는 장점들은 다음과 같습니다.
* applying specific transaction semantics (such as custom isolation level or transaction timeout)
* retrieving the transactional JDBC Connection (for exposure to JDBC-based DAOs)
* advanced translation of PersistenceExceptions  to Spring DataAccessExceptions

JDBC connection의 편리한 사용과 Transaction 그리고 DataAccessExcepion에 대한 장점은 10장, 11장에 이어 12장에서 까지 계속 얘기하네요.
top