Whiteship's Note

12.2.3. The HibernateTemplate

Spring/Chapter 12 : 2007. 4. 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() 메소드 소스 코드를 보시면 어떤 일을 해주는지 짐작할 수 있습니다.

한 단계 더 나가서 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

  1. Favicon of http://chanwook.tistory.com BlogIcon 찬욱 2007.04.23 19:39 신고 PERM. MOD/DEL REPLY

    이상하게 이 글로 트랙백이 안가요 -0-; 왜그럴까요~

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2007.04.23 21:12 PERM MOD/DEL

    흠;;; 글쎄 왜그러지;; 댓글로라도 링크 주렴~

  2. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2008.05.13 17:34 PERM. MOD/DEL REPLY

    HibernateTemplate 뜯어 봤는데...
    만약 트랜잭션매니저를 사용중이면 절대 flush하지 않고 close하지도 않더군요..
    반면 트랜잭션매니저를 사용하지 않을 경우 flush는 모르겠고 close하구여
    오퍼레이션시 예외 발생하면 Spring 공통 런타임 예외인 DataAccessException으로 번역하는거..
    이거 말고는 딱히 다른 작업을 하지 않은것 같습니다... 저는 솔직히 HibernateTemplate 쓰는 장점 모르겟어요 어차피 Hibernate Session으로 작업해도 체크되지 않은 예외 던지기 때문에 예외번역의 효과는 미미하고.. 트랜잭션관리야 뭐 서비스레이어에서 @Transactional로 처리하는거고... 큰 장점은 모르겠네요..
    iBatis일 경우는 sqlMapClient의 메서드가 SQLException을 던지기 때문에 예외번역 효과가 커서 이익이지만... Hibernate의 경우 큰 메리트는 못느끼겠네요... 기선님이 장점에 대해서 좀 알려주세요...

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.05.13 18:20 신고 PERM MOD/DEL

    맞습니다. 저도 요즘하고 있는 프로젝트에선 HibernateTemplate이나 HibernateSupport 클래스를 사용하지 않습니다.

    굳이 장점이라고 한다면, 템플릿 코드를 사용해서 세션 열고 닫는 일을 줄여주고, 언급하신 DAE으로의 변경인데요.

    트랜잭션을 Service 계층에서 열고 닫기로 하면(서비스 계층에 @Transactional 붙여주면 그렇게 되죠.) DAO에서는 getCurrentSession()으로 Session 가져와서 원하는 작업만하면 됩니다. 기타 Session 열고 닫고 트랜잭션 열고 커밋 또는 롤백하는 일은 서비스 계층에 적용된 트랜잭션 매니저가 알아서 해주겠죠.

    남은 하나는, 하이버예외를 스프링의 DAE(Data Access Exception)으로 바꾸는 작업인데, 이 작업 마저도 하이버 3쩜에 와서 별로 필요없어진게.. 하이버도 3쩜대 부터는 Uncached Exception으로 예외 타입을 바꿨고, 스프링 만큼이나 풍부한(거의 동일한 갯수 였던걸로 기억하는데 자세힌 몰겠습니다.) 예외 계층 구조를 가지고 있습니다. 그래도 굳이.. 또 스프링 DAE로 바꾸고 싶다면, 간단한 Aspect 하나만 만들어서 적용하면 됩니다.(http://whiteship.tistory.com/1491)

    따라서.. '기호'의 문제이지만, 전 보다 코드가 간결하고 깔끔한 getCurrentSession()이 더 좋습니다. DAO에 SessionFactory를 DI하고 사용하는거죠.

  3. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.05.13 17:55 신고 PERM. MOD/DEL REPLY

    결정적으로 페이징 메서드를 제공하지 않더군요..
    기선님은 이부분 어떻게 해결하시나요?

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.05.13 18:38 신고 PERM MOD/DEL

    이게 화면의 그리드 부터 컨트롤러, 서비스, DAO까지 쭉 연결되는 거라 간단하지 않은것 같습니다.

    스프링이 제공하는 PagedListHolder라는 걸 확장해서(아니면 새로운 클래스를 만들어서) 하이버네이트의 Criteria에 페이징 관련 설정을 추가해주는 등의 페이징 기능을 하는 메소드를 구현하셔야 될 것 같습니다.

  4. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.05.13 19:29 신고 PERM. MOD/DEL REPLY

    페이징 구현하기도 귀찮고 어차피 체크되지 않은 예외던지기고 한마디로 Hibernate는 걍 오리지날 방식이 편할듯 싶네요... 혼자 이걸 어떻게 써야 이득을 볼까 고민 했는데 기선님 말씀 듣고보니 사용할 필요성이 팍 없어지네요..
    단 iBATIS의 경우는 예외번역의 메리트가 있기 때문에 Template을 쓰는게 좋겠네요~

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.05.13 19:55 신고 PERM MOD/DEL

    페이징 기능은 OSAF 2.0이 공개되면 알아서 해줍껍니다.
    캬캬캬 ^^

    하이버 3쩜대를 사용할 경우 HibernateTemplate은 별 메릿트가 없습니다. 괜히 코드만 지져분해지는 느낌이죠. getHibernateTemplate().excecute(new HibernateCallBack() { //진짜 필요한 코드 }); 이런식이니까요.

  5. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.05.13 20:11 신고 PERM. MOD/DEL REPLY

    우엇 OSAF 2.0 기대 댑니당
    OSAF에서 어떤 기능을 제공 하나영?

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.05.13 20:44 신고 PERM MOD/DEL

    Generic(DAO, Service, Controller, PropertyEditor), Code Generation, Custom Tag(슈퍼 초 울트라 올마이티 Grid 포함, IE/FF 호환 가능 웹 표준), Convention Over Configuration, Web Support(Paging), Test Support, Spring Security Support, 각종 Utils 및 기타 등등등 으로 계속해서 기능 추가 및 개발 중입니다.

    올해가 가기전에 일부는 공개 할지도...

  6. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2008.05.13 22:48 PERM. MOD/DEL REPLY

    테스트 해봤는데 결과부터 말씀드리자면
    DAO에서 sessionFactory.getCurrentSession() 으로 가져오면 안되는 군요...
    안되는 이유는 이렇습니다.
    getCurrentSession()으로 가져온 Session은 Proxy 객체인거는 당연히 아시겠지만...
    그 Proxy안에 들어 있는 Session은 서비스 레이어에서 @Transactional 애노테이션으로 인해 적용된
    TransactionInterceptor에서 트랜잭션을 시작한 Session과 서로 다르더군요...
    따라서 getCurrentsession로 얻어온 세션은 반드시 트랜잭션을 시작해야 하는데 그렇지 못하니까
    트랜잭션이 시작되지 않았다는 예외가 발생 합니다.
    서비스 레이어에 @Transactional로 열려진 트랜잭션의 세션을 가져오려면 HibernateDaoSupport의 getSession() 메서드을 통해야만 동일한 세션을 가져오네요...

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.05.13 23:09 PERM MOD/DEL

    흠.. 그럴리가요. 엄연히 스프링 레퍼런스에도 나와있는 방법인데다가. http://whiteship.tistory.com/752 저는 현재 프로젝트에서 그런식으로 잘 사용하고 있습니다. 뭔가 설정을 잘못하신거 아닐까요?

    게다가 정말 getCurrentSession()이 반환한는 Session 객체가 Proxy인가요?? 흠;;; 몰랐는데요. Session 객체를 갈아치울 일도 없고 나중에 가져와야할 필요도 없는데 왜 프록시 객체일까요??

    테스트 하신 코드와 XML, 로그메시지 등을 째코님 블로그에 올려주실 수 있나요? 궁금합니다. 전 그렇게 매우 깔끔하게 잘 쓰고 있거든요;;

  7. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2008.05.13 23:35 PERM. MOD/DEL REPLY

    앗... 삽질 했네요. ㅋㅋㅋㅋ
    문제가 뭔지 알아 냈습니다.
    <prop key="hibernate.current_session_context_class">thread</prop>
    이 설정 때문이군요... -0-;
    위 설정을 제거 하면 디폴트로 SpringSessionContext가 등록되는데 소스를 확인해보니 getCurrentSession() 구현이 서비스레이어에서 열어놓은 세션을 가져오도록 되어 있네요..
    저것의 설정값을 뭘로 해야될까 생각하다가 기선님 블로그에서 스크린캐스팅에 thread로 되어 있길래 -_- thread 구나! 하고 설정 한건데 서비스레이어에서 열어놓은 세션이 저장되는 곳은 쓰레드로컬이 아닌 Spring자체 SessionContext라서 그 동안 서로 엄한거 찾아 와서 그랬나봐요..
    오늘 SpringSessionContext가 있다는 사실은 첨 알았네요..

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.05.13 23:51 PERM MOD/DEL

    네 하이버네이트의 CurrentSessionContext 인터페이스를 스프링이 구현해서 스프링이 관리하는 현재 Session 객체를 반환하도록 구현한 클래스죠.

    하이버네이트가 확장지점 만들어주고 스프링이 그걸 확장해서 스프링이 원하는 방식(비침략적인 방식)으로 구현해서 끼워넣은거죠. 사이좋은 스프링과 하이버.. 그로 인해 하이버의 SessionFactory만 사용해서 DAO를 구현하는 것이 가능해진거라고.. 토비 사부한테 배웠는데 이렇게 다시 알게 되다니 재밌네요.

    훔.. 근데 이상한건... 전 hibernate.current_session_context_class 이 설정 모르던 건데요. ㅋㅋㅋ 어디서 보신거에요?

    이거 이거 동영상 올리며 안 되는겠는데요~~ 제가 쓰는 세션팩터리 설정은 네 다섯개 밖에 안 되는데요.

    <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
    <prop key="hibernate.show_sql">true</prop>
    <prop key="hibernate.format_sql">false</prop>
    <prop key="hibernate.hbm2ddl.auto">update</prop>
    <prop key="hibernate.connection.autocommit">false</prop>

    그리고 아시겠지만, getCurrentSession()이 반환하는 Sssion 객체는 Proxy가 아니라 그냥 일반 객체죠? 프록시 객체일 필요가 없자나요.

  8. Favicon of http://jjaeko.tistory.com BlogIcon 째코 2008.05.14 00:24 PERM. MOD/DEL REPLY

    아까 제가 Proxy라고 말했던 상황은 제가 실수해서 session_context를 쓰레드로컬로 잘못 지정한 상황이었죠... ThreadLocalSessionContext 의 currentSession 메서드에서 다음 메서드를 호출 하네요..
    protected Session wrap(Session session) {
    TransactionProtectionWrapper wrapper = new TransactionProtectionWrapper( session );
    Session wrapped = ( Session ) Proxy.newProxyInstance(
    Session.class.getClassLoader(),
    SESS_PROXY_INTERFACES,
    wrapper
    );
    // yick! need this for proper serialization/deserialization handling...
    wrapper.setWrapped( wrapped );
    return wrapped;
    }
    JDK Proxy 사용하는거 맞죠?

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.05.14 08:28 PERM MOD/DEL

    흠~ 그렇군요. 저런 클래스가 있는지도 몰랐네요.

Write a comment.