레퍼런스 12장에 오타

문제가 발생한 코드는 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 사용해서 예제 수정 해주세욜~

12.2.4. Implementing Spring-based DAOs without callbacks

콜백 메소드를 사용하지 않고 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을 사용하는게 편할 것 같습니다.

12.2.3. The HibernateTemplate

이 전 글에서 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() 메소드 소스 코드를 보시면 어떤 일을 해주는지 짐작할 수 있습니다.
[#M_ more.. | less.. |     public Object execute(HibernateCallback action, boolean exposeNativeSession) throws DataAccessException {
        Assert.notNull(action, “Callback object must not be null”);

        Session session = getSession();
        boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session, getSessionFactory());
        if (existingTransaction) {
            logger.debug(“Found thread-bound Session for HibernateTemplate”);
        }

        FlushMode previousFlushMode = null;
        try {
            previousFlushMode = applyFlushMode(session, existingTransaction);
            Session sessionToExpose = (exposeNativeSession ? session : createSessionProxy(session));
            Object result = action.doInHibernate(sessionToExpose);
            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 {
            if (existingTransaction) {
                logger.debug(“Not closing pre-bound Hibernate Session after HibernateTemplate”);
                if (previousFlushMode != null) {
                    session.setFlushMode(previousFlushMode);
                }
            }
            else {
                // Never use deferred close for an explicitly new Session.
                if (isAlwaysUseNewSession()) {
                    SessionFactoryUtils.closeSession(session);
                }
                else {
                    SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
                }
            }
        }
    }_M#]
한 단계 더 나가서 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 해야하는 수고를 덜 수 있겠습니다.

12.2.2. SessionFactory setup in a Spring container

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>

12.2.1. Resource management

자원을 관리하는 코드가 보통 사방에 흩어져 있게 됩니다. 하지만 스프링에서는 이런 자원 관리간단하면서도 강력한 방법인 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.