MyBatis는 ORM이 아니다.

구 iBatis, 현재 MyBatis는 ORM(Object Relational Mapping이 아니다.

http://en.wikipedia.org/wiki/Object-relational_mapping

http://www.mybatis.org/

이 두개 링크만 봐도 알 수 있지만, MyBatis 홈페이지에도 ORM이라는 말은 전혀 나오지 않는다. 그런데도 일부 MyBatis와 JPA 류를 같은 ORM이 아닌가 혼동하는 분들이 있다.

ORM은 간단하게 말해서 Object와 Relataion 간의 불일치 문제(단위, 상속, 데이터 타입)등등 여기 나와있는 문제를 해결해주는 도구다.

MyBatis가 저기 나열된 문제 중에 뭘 해결해주는가? 없다. ORM이 아니다. 그냥 자기 홈피에 써있는데로 단순한 데이터 맵퍼일 뿐이다.

12.2.8. Transaction management strategies

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를 가져 올때 걍 평범하게 가져오면 됩니다.

12.2.7. Declarative transaction demarcation

이번엔 선언적인 트랜잭션 경계 설정에 관해 살펴보겠습니다. 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>

12.2.6. Programmatic transaction demarcation

프로그래밍적인 방법으로 트랜잭션 경계를 지정하는 방법은 다음과 같이 비즈니스 로직 부분에 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를 사용하여 따로 빼낼 수 있는데요. 그런 방법을 다음 챕터인 선언적인 트랜잭션 경계 선언에서 알아보겠습니다.

12.2.5. Implementing DAOs based on plain Hibernate3 API

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만 사용하여 구현할 수도 있습니다.