Spring - Hibernate 연계사용시 Tips#

이 글은 Spring-Hibernate 사용시의 몇가지 도움이 될만한(?) 내용을 담고 있습니다. 여기에서 기술되지 않는 Spring 에 대한 기본적인 설명은 'Business Layer' 게시판의 간단한 Spring 설정 xml 파일 사용 예시을 참조하도록 하시고, Hibernate 관련 설명은 'Persistence Layer' 게시판의 초보자를 위한 Hibernate 사용 Tips(1), 초보자를 위한 Hibernate 사용 Tips(2)를 참조하세요.

1) hibernate.cfg.xml 파일을 applicationContext.xml 파일에 통합하기

Spring 의 가장 큰 장점 중의 하나는 Spring 이 지원하는 모든 라이브러리들은 applicationContext.xml 파일 하나에서 관리 가능하다는 점이라고 생각한다. Hibernate 의 설정을 관리하는 hibernate.cfg.xml(물론, hibernate.properties 를 사용해도 상관없다.) 파일은 Hibernate 의 SessionFactory 관련 설정을 정의한다. 주되게는 datasource, dialect(기반 DBMS 가 무엇인지에 관한 설정), mapping file(DBMS 의 table 과 매핑되는 POJO 정보를 담은 hbm 파일) 등의 정보를 가지고 있다. 이 정보들을 applicationContext.xml 파일로 옮기는 것은 hibernate.cfg.xml 에서의 property 들이 Spring 에서의 bean 혹은 property 로 그대로 매치되기 때문에 간단한 작업이다.

>> hibernate.cfg.xml 사용예시

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration
     PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
     "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

    <hibernate-configuration>
        <session-factory>
            <property name="connection.datasource">java:comp/env/jdbc/test</property>
            <property name="show_sql">true</property>
            <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        
            <!-- Mapping files -->
            <mapping resource="test/Woman.hbm.xml"/>
            <mapping resource="test/Clothes.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>

>> 위의 hibernate.cfg.xml 를 applicationContext.xml 로 변경시

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
        
    <beans>
      <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
          <value>java:comp/env/jdbc/test</value>
        </property>
      </bean>
          
      <!-- Choose the dialect that matches your "dataSource" definition -->
      <bean id="mySessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="dataSource"><ref bean="myDataSource"/></property>
        <property name="mappingResources">
          <list>
            <value>test/Woman.hbm.xml</value>
            <value>com/nextware/board/domain/Board.hbm.xml</value>
            <value>test/Clothes.hbm.xml</value>
          </list>
        </property>
            
        <property name="hibernateProperties">
          <props>
            <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
          </props>
        </property>
      </bean>
    </beans>

여기의 applicationContext.xml 은 오로지 위의 hibernate.cfg.xml 파일 내용만을 옮겨둔 간략한 형태이다. 거의 비슷하다는 느낌을 받을 수 있을 것이다. 다른 점이라면,

  1. hibernate.cfg.xml 의 property 중 datasource 가 별도의 bean 으로 처리되고 있다는 점(예에서는 JNDI datasource 를 사용하고 있지만, org.apache.commons.dbcp.BasicDataSource 라던가 어떤 다른 datasource 도 사용가능하다. 중요한 것은 bean 으로 만드는 것 뿐이다.)
  2. datasource 를 제외한 나머지 property 들은 <props> 태그 안에서 property 로 한꺼번에 처리된다는 점
  3. mapping resource 들이 mapping resources property 로 묶여서 개별 hbm 파일들이 <list> 태그로 묶여서 관리된다는 점
정도가 다를 뿐이다.

이 외에도 사용될 수 있는 property 들에는 show_sql, query.substitutions, cache.use_query_cache, cache.provider_class, statement_cache.size 등등 많지만 자세한 사항은 여기에서 다루지는 않을 것이다. (이 글은 초보적인 사용에 초점이 맞추어져 있으며, 고급의 설정 및 사용법까지 다루지는 않는다. 무엇보다도 cash 에 대한 세부적이고 자세한 사항은 나 역시 공부를 더 해야 하는 관계로... 쿨럭;;; -0-a)

2) Hibernate 의 Session 인터페이스를 대체하는 Spring 의 HibernateTemplate 클래스 및 HibernateDaoSupport 클래스

Hibernate 에서 모든 CRUD 는 Session 인터페이스를 경유하여 이루어지게 되어 있었다. Spring 은 Session 인터페이스에서 지원하는 모든 CRUD 관련 동작들과 이 동작들에 대한 트랜잭션까지 관리해주는 HibernateTemplate 클래스를 제공하고 있으며, 이 HibernateTemplate 및 Hiberante SessionFactory 및 Session 을 관리해주는 HibernateDaoSupport 클래스를 제공한다. 때문에, 개발자들은 HibernateDaoSupport 를 상속받은 dao 클래스에서 HibernateTemplate 를 마음대로 가져다 쓸 수 있으며, 별다른 구현없이 메써드 호출만으로 간단하게 Hibernate 를 사용할 수 있다. (HibernateDaoSupport 클래스에서 제공하는 Session의 경우 protected 로 제한되어 있으며, 그 자신이 abstract 클래스이므로, 상속은 필수적이다. 단, 사용자가 별도로 HibernateDaoSupport 클래스 역할을 하는 클래스를 만든다면 상관없겠지만, 굳이 그럴 필요는 없다! 고 단언한다.)

1. HibernateTemplate 클래스의 public Objectexecute(HibernateCallback action) 및 public List executeFind(HibernateCallback action) 메써드

HibernateTemplate 클래스는 Session 인터페이스의 find, get 메써드는 executeFind 메써드를 통해, 나머지 CRUD 동작 관련한 메써드는 execute 메써드를 통해 처리한다. 이들 메써드는, Session 인터페이스를 사용한 데이터 처리내용이 구현되어 있는, HibernateCallback 인터페이스 타입의 클래스를 인자로 받아서 실행하며, 이 실행은 트랜잭션 처리가 되어 있다.

>> HibernateTemplate 클래스 소스 일부분

    public Object execute(HibernateCallback actionthrows DataAccessException {
      Session session = (!isAllowCreate() ?
          SessionFactoryUtils.getSession(getSessionFactory()false:
          SessionFactoryUtils.getSession(
              getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()));
      boolean existingTransaction = TransactionSynchronizationManager.hasResource(getSessionFactory());
      if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {
        session.setFlushMode(FlushMode.NEVER);
      }
      try {
        Object result = action.doInHibernate(session);
        flushIfNecessary(session, existingTransaction);
        return result;
      catch (HibernateException ex) {
        throw convertHibernateAccessException(ex);
      catch (SQLException ex) {
        throw convertJdbcAccessException(ex);
      catch (RuntimeException ex) {
        // callback code threw application exception
        throw ex;
      finally {
        SessionFactoryUtils.closeSessionIfNecessary(session, getSessionFactory());
      }
    }

    public List executeFind(HibernateCallback actionthrows DataAccessException {
      return (Listexecute(action);
    }

2. HibernateDaoSupport 클래스

>> HibernateDaoSupport 클래스의 주요 메써드는

public final void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
  this.hibernateTemplate = hibernateTemplate;
}
public final HibernateTemplate getHibernateTemplate() {
  return hibernateTemplate;
}
public final void setSessionFactory(SessionFactory sessionFactory) {
  this.hibernateTemplate = createHibernateTemplate(sessionFactory);
}
public final SessionFactory getSessionFactory() {
  return (this.hibernateTemplate != null this.hibernateTemplate.getSessionFactory() null);
}
protected final Session getSession()
    throws DataAccessResourceFailureException, IllegalStateException {
  return getSession(this.hibernateTemplate.isAllowCreate());
}
protected final Session getSession(boolean allowCreate
  throws DataAccessResourceFailureException, IllegalStateException {
  return (!allowCreate ? SessionFactoryUtils.getSession(getSessionFactory()false
  SessionFactoryUtils.getSession(getSessionFactory()this.hibernateTemplate.getEntityInterceptor()
  this.hibernateTemplate.getJdbcExceptionTranslator()));
}

등이 있다. HibernateDaoSupport 클래스는 SessionFactory 를 applicatinContext.xml 의 설정을 통해 넘겨받고, 이 넘겨받은 SessionFactory 를 통해 Session 과 HibernateTemplate 인스턴스를 생성해 넘겨주는 헬퍼 클래스의 역할을 수행한다.

3. HibernateTemplate 의 한계 및 확장 HibernateTemplate 클래스는 거의 Session 인터페이스의 래퍼 클래스라고 생각하면 될 것이다. 그러나, 아쉽게도 페이징 처리와 같이 Session 인터페이스에 직접 구현되어 있지 않고, Query 인터페이스를 통해 사용자가 직접 구현해야 하는 기능은 HibernateTemplate 클래스에서 제공해주지 않는다. 또한, 페이징 이외에도 여러 개의 동작들을 하나의 트랜잭션으로 묶어야 할 필요가 있는 경우 역시 HiberanteTemplate.save() 등을 사용해서는 안된다. 왜냐하면, 위에서 본 바와 같이 HibernateTemplate 에서 제공하는 메써드들은 이들 각각이 HibernateTemplate.execute() 혹은 executeFind() 메써드를 호출하고 있으며, 이 메써드들은 그 내부적으로 트랜잭션을 별개로 완료하고 있기 때문이다.(위의 소스 참조)

그렇다면, 지금 언급한 페이징 처리 혹은 트랜잭션 처리는 어떤 방법으로 가능한가?

>> 일반적인 사용

public class TestDao extends HibernateDaoSupport {
    public List getList() {
        return getHibernateTemplate().find("from Woman");
    }
    
    public Serializable save(Object bean) {
        return getHibernateTemplate().save(bean);
    }
}

>> 기존 JDBC 사용과 유사한 일반적인 처리방법

public class TestDao extends HibernateDaoSupport {
    public List getPagedList() {
        Session session = null;
        List resultList = null;
        try {
            session = getSession();
            Query query = session.createQuery("from Woman");
            query.setFirstResult(0);
            query.setMaxResults(10);
            
            resultList = query.list();
        }catch(DataAccessResourceFailureException darfe) {
            darfe.printStackTrace();            //getSession() 시 에러
        }catch(IllegalStateException ise) {
            ise.printStackTrace();              //getSession() 시 에러
        }catch(HibernateException he) {
            he.printStackTrace();               //hibernate 에러
        }catch(Exception e) {
            e.printStackTrace();//일반적인 에러
        }finally {
            closeSessionIfNecessary(session);
        }
      
        return resultList;
    }
  
    public void multiSave(List list) {
        Session session = null;
        
        try {
            session = getSession();
            tx = session.beginTransaction();
            
            session.save(list.get(0));
            session.save(list.get(1));
            session.update(list.get(2));
            
            tx.commit();
        }catch(DataAccessResourceFailureException darfe) {
            tx.rollback();
            darfe.printStackTrace();            //getSession() 시 에러
        }catch(IllegalStateException ise) {
            tx.rollback();
            ise.printStackTrace();              //getSession() 시 에러
        }catch(HibernateException he) {
            tx.rollback();
            he.printStackTrace();               //hibernate 에러
        }catch(Exception e) {
            tx.rollback();
            e.printStackTrace();//일반적인 에러
        }finally {
            closeSessionIfNecessary(session);
        }
    }
}

>> HibernateTemplate 의 execute, executeFind 메써드에 트랜잭션 처리를 위임하는 방식

public class TestDao extends HibernateDaoSupport {
    public List getPagedList() {
        return getHibernateTemplate().executeFind(new HibernateCallback() {
            public Object doInHibernate(Session session)
    throws HibernateException {
                Query query = getHibernateTemplate().createQuery(session, "from Woman");
                query.setFirstResult(0);
                query.setMaxResults(10);

                return query.list();
            }
        });
    }
    
    public void multiSave(final List list) {
        getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session)
    throws HibernateException {
                session = getSession();
                session.save(list.get(0));
                session.save(list.get(1));
                session.update(list.get(2));

                return null;
            }
        });
    }
}

>> 주의

  1. 위에서 사용된 HibernateTemplate.createQuery(), createCriteria() 은 Deprecated 되었는데, 과거 Session.createQuery() 메써드 등이 Query 의 캐쉬사용과 트랜잭션의 타임아웃 설정이 되지 않았던 반면, 이제 Session.createQuery() 메써드 등에서 자체적으로 캐쉬와 타임아웃을 자동지원함에 따라 Deprecated 되었다.
  2. 그래도, createQuery()를 사용하고자 한다면, HibernateCallback() 인터페이스의 doInHibernate() 메써드 구현시 사용할 것을 권장하고 있다.
  3. 위의 multiSave(final List list) 와 같이 익명클래스로 HibernateCallback 인터페이스를 구현할 때, 메써드의 지역변수를 사용해야 할 경우, final 선언을 해주어야 한다.(이것은 자바 규칙이므로 별다른 설명을 덧붙히진 않겠다.)
  4. MySQL 4.x 버전(정확한 버전은 기억이... -0-) 부터 default storage engine 은 MyISAM 으로 설정되는데, 이 MyISAM 의 경우 트랜잭션 처리가 되지 않는다. 따라서, 트랜잭션 처리가 반드시 이루어져야 할 테이블의 경우엔 InnoDB 로 storage engine 타입을 변경해주어야 한다.(MySQL Query Browser 로 간단하게 변경가능하다.)

3) HibernateDaoSupport 를 상속한 예제 Dao 클래스 및 applicationContext.xml 에서의 bean 매핑

여기에서는 몇개의 예제 클래스 및 applicationContext.xml 파일 설정, 그리고 applicationContext.xml 파일을 읽어들인 context 로부터 service bean 클래스를 가져다 쓰는 방법까지를 간략하게 기술할 것이다.

  • BaseHibernateDaoSupport.java

     package test.comm;
     
     import net.sf.hibernate.HibernateException;
     import net.sf.hibernate.Query;
     import net.sf.hibernate.Session;
     
     import org.springframework.orm.hibernate.HibernateCallback;
     import org.springframework.orm.hibernate.support.HibernateDaoSupport;
     
     public abstract class BaseHibernateDaoSupport extends HibernateDaoSupport {
       
         /**
         * return list data with paging
         
         @param String queryName
         @param int page
         @param int range
         @return List
         @throws HibernateException
         */
        public List getPagedList(final String queryName, final int page,
                final int range) {
        
           return getHibernateTemplate().executeFind(new HibernateCallback() {
               public Object doInHibernate(Session session)
                       throws HibernateException {
                   Query queryObject = getHibernateTemplate().getNamedQuery(
                           session, queryName);
                   queryObject.setFirstResult((page - 1* range);
                   queryObject.setMaxResults(range);
           
                   return queryObject.list();
               }
           });
        }
        
        ... //기타 사용자 정의 메써드들
     }

  • WomanDao.java

     package test;
     
     import java.util.List;
     
     public interface WomanDao {
         List getWomanList(String queryStr);
         List getPagedWomanList(int page, int range);
         Long saveWoman(Object bean);
     }

  • WomanDaoImpl.java
     package test;
     
     import java.util.List;
     import test.comm.BaseHibernateDaoSupport;
     
     public class WomanDaoImpl extends BaseHibernateDaoSupport implements WomanDao {
         public List getWomanList(String queryStr) {
             return getHibernateTemplate().find(queryStr);
         }
         
         public List getPagedWomanList(int page, int range) {
             return getPagedList("testQuery", page, range);
         }
         
         public Long saveWoman(Object bean) {
             return (Long)getHibernateTemplate().save(bean);
         }
     }
  • applicationContext.xml 파일 설정

>> 위에서 설명한 SessionFactory 설정은 생략

      <bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory"><ref local="mySessionFactory"/></property>
      </bean>
      
      <bean id="womanTarget" class="test.WomanDaoImpl">
        <property name="sessionFactory"><ref bean="mySessionFactory"/></property>
      </bean>
      <bean id="womanService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">    
        <property name="transactionManager"><ref bean="myTransactionManager"/></property>
        <property name="target"><ref local="womanTarget"/></property>
        <property name="transactionAttributes">
          <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly,-org.springframework.dao.DataAccessException</prop>
            <prop key="save*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
            <prop key="delete*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
            <prop key="update*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
          </props>
        </property>
      </bean>

  1. Hibernate 트랜잭션을 관리해주는 Spring 의 HibernateTransactionManager 클래스를 bean 으로 만들고 이 bean 을 통해 Session 을 사용한다.
  2. womanService bean 은 이후 실제 사용하게 될 bean 인데, womanTarget 으로 명명된 test.WomanDaoImpl 클래스에 aop 기술을 통해 트랜잭션 로직을 끼워넣은(intercept) 것이다.
  3. 여기서 transactionAttributes 의 props 로 지정되는 내용은 각각의 메써드명의 타입에 따른 트랜잭션 정책을 의미하는데, (위의 파일을 예로 들어보자면) get 으로 시작하는 메써드는 readOnly, 즉 트랜잭션 처리를 하지 않으며, save, delete, update 로 시작하는 메써드는 org.springframework.dao.DataAccessException 익셉션 발생시 트랜잭션 rollback 을 실행하라는 의미이다.
  • service bean 의 사용

      <bean id="myTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory"><ref local="mySessionFactory"/></property>
      </bean>
      
      <bean id="womanTarget" class="test.WomanDaoImpl">
        <property name="sessionFactory"><ref bean="mySessionFactory"/></property>
      </bean>
      <bean id="womanService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">    
        <property name="transactionManager"><ref bean="myTransactionManager"/></property>
        <property name="target"><ref local="womanTarget"/></property>
        <property name="transactionAttributes">
          <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly,-org.springframework.dao.DataAccessException</prop>
            <prop key="save*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
            <prop key="delete*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
            <prop key="update*">PROPAGATION_REQUIRED,-org.springframework.dao.DataAccessException</prop>
          </props>
        </property>
      </bean>

      package test;
      
      import org.springframework.context.ApplicationContext;
      
      public class TestDao {
          public static ApplicationContext ctx = new FileSystemXmlApplicationContext(./applicationContext.xml);
      
          public static void main(String[] args) {
              WomanDao dao = (WomanDao)ctx.getBean("womanService");   //인터페이스 타입으로 받아서 사용한다. IoC 패턴
              List womanList = dao.getWomanList("from Woman");
              
              for(int i=0; i&lt;womanList.size(); i++) {
                  System.out.println("womanList.get("+i+") : " + womanList.get(i));
              }
          }
      }

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor