Whiteship's Note

[하이버네이트] 애매한 에러 메시지 때문에 삽질.. @_@

Hibernate/etc : 2009.08.07 21:50


DEBUG - OpenSessionInViewFilter.doFilterInternal(207) | Closing single Hibernate Session in OpenSessionInViewFilter
DEBUG - SessionFactoryUtils.closeSession(784) | Closing Hibernate Session
2009. 8. 7 오후 9:30:52 org.apache.catalina.core.StandardWrapperValve invoke
심각: Servlet.service() for servlet springsprout threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: springsprout.domain.Member.studies, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:343)
    at org.hibernate.collection.PersistentSet.add(PersistentSet.java:189)
    at springsprout.domain.Member.addManegedStudy(Member.java:154)
    at springsprout.modules.study.StudyService.add(StudyService.java:24)
    at springsprout.modules.study.StudyService$$FastClassByCGLIB$$6d06dbc0.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
...

에러의 원인이 무엇일까? 에러 로그에서 가장 중요한 부분이라고 생각하는 부분을 진하게 표시했다. 제 1의 단서는 에러 메시지이고, 제 2의 단서는 에러가 발생한 지점 중 내가 코딩을 한 부분이다. 스프링, 하이버는 많은 유저와 테스트 그리고 지속적인 관리를 통해 내가 작성한 코드보다 더 신빙성이 높기 때문이다.

어쨋든.. 생각해보자. 에러 메시지가 뭐라고 하는가?? 세션이 없다고?? 왜??? 난 분명 @Transactional을 사용했다. 트랜잭션이 없을리 없는데.. 왜 그럴까.. 이해가 되지 않는다. 세션이 닫혔다고?? 왜??? 이해가 되지 않는다. 이런 일이 발생한 적은 없었다.

혹시나해서 DEBUG 모드로 스프링 로그를 찍어보았다. 해당 에러가 나기 직전에 이러한 로그를 볼 수 있다.

DEBUG - HibernateTransactionManager.doBegin(504) | Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@7650bc]
DEBUG - HibernateTransactionManager.doBegin(569) | Exposing Hibernate transaction as JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@a681c3]
DEBUG - HibernateTransactionManager.doGetTransaction(437) | Found thread-bound Session [org.hibernate.impl.SessionImpl@7650bc] for Hibernate transaction
DEBUG - AbstractPlatformTransactionManager.handleExistingTransaction(468) | Participating in existing transaction

분명 Session을 잘 가져왔고, 세션이 닫힌다는 얘기도 없다. 이 바로 다음에 에러가 발생한다. 막막하군.. 그럼 이제 내가 작성한 코드를 살펴보자.

    public void add(Study study) {
        Member currentMember = securityService.getCurrentMember();
        currentMember.addManegedStudy(study);
        repository.add(study);
    }

    public void addManegedStudy(Study study) {
        getStudies().add(study);
        study.addMember(this);
        getManagedStudies().add(study);
        study.setManager(this);
    }

빨간색으로 표시한 부분이 에러가 발생하는 지점이고 최종적으로 getStudies()를 호출할 때 발생한다. 모르겠다. 무엇이 문제일까?

테스트를 만들어보자.

    @Test
    public void add() throws Exception {
        Study study = new Study();
        Member member = new Member();
        memberService.addWithoutConfirm(member);
       
        service.add(study, member);

        assertEquals(member, study.getManager());
        assertTrue(study.getMembers().contains(member));
    }

스프링 통합 테스트를 만들어서 테스트해보았다. 이 테스트는 잘 돌아간다. 더 미궁속으로 빠져든다. 그런데, 테스트와 실제 코드가 같지 않다. 테스트를 편하게 하기 위해 조금 다른 코드를 이용했다. 둘의 차이는 무엇일까?

테스트에서의 member 객체 상태는 Persistent다. 그러나... 실제 코드에서 member 객체 상태는 어떨까? 아차차.. 이런.. 문제의 실마리가 보이는 듯 하다.

그렇다. 실제 코드에서 currentMember 객체는 detached 상태인 것이다. 아.. 이런.. 단서에 너무 의존하다가 눈앞에 있는 객체 상태를 보지 못했다. 나의 실수다. 하이버네이트를 다룰 때 가장 중요한 것 중 하나가 바로 객체의 상태인데. 그것을 못 보다니 한심하다는 생각이 밀려온다. 이미 지난 일이다. 어쩔 수 없다. 다음번엔 잘 찾아내야지.

단 하나.. 바라는게 있다면, 세션이 없다는 얼토당토 않은 에러 메시지 말고, detached 객체에서 lazy loading이 발생한다고 메시지를 출력해주면 좋겠다. 그랬다면... 좀 더 쉽게 해결할 수 있었을 법한 문제였다.
top

  1. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.08.08 01:06 PERM. MOD/DEL REPLY

    이거는 Hibernate 사용자라면 적어도 한번쯤은 겪는 그런 문제인거 같네요. :)

    본문에 다른부분까지 있는 좀더 자세한 코드가 없어서 확신할수는 없지만,
    Hibernate 입장에서는 저문제가 Session이 없는게 (닫힌게) 맞을껍니다. :)
    그러니까 detached 상태인 Entity object의
    field를 접근할때 이녀석이 lazy loading 돼야하는 field면
    아마 Hibernate가 얘를 initialise 시켜서 lazy loading을 시도 하는걸로
    알고 있습니다. (아...아닌가?ㅡ_ㅡ?)
    아무튼 그 시도를 위해서 Session을 찾는데, 쓸수있는 세션이 없겠죠.
    Spring 쓰시면서 @Transactional을 쓰셨을텐데 @Transactional 설정한 method
    실행이 종료되면서 Session이 닫혀 버렸을테니까요.
    그러니까,
    Entity object의 field를 lazy loading 해야함 -> 어? detached 됐네? -> 알았어 너를 다시 Entity로 만들어서 읽어줄께 -> Session아 어딨니? -> 어? Session이 닫혔네? -> 에라이, LazyInitializationException!
    요렇게 되는거 같습니다. 아... 사실 이건 저한테 발생했던 상황입니다...ㅡ_ㅡ;;;

    방법이야 LAZY fetching 말고 EAGER fetching을 하면 되긴하는데,
    안 쓸지도 모르는 field를 꼭 eager fetching 한긴 싫고...

    저는 View쪽에서 XML 생성하는데 lazy loading 설정한 부분에서 저 Exception이 발생해서
    해결방법으로 혼자서 그냥 알아낸 이상한(?) 방법이...
    createQuery() method를 써서 query (e.g. "from EntityObject id = :id" ) 로
    Entity를 가져오고 이 method에는 @Transactional 설정을
    안 하는겁니다. 그럼 나중에 lazy loading 할때 제대로 되더군요.
    근데 이렇게 해도 entityManager.find() 메소드를 쓴 경우는 안 되고 역시 LazyInitializationException...
    역시 이 방법은 좀 아닌거 같아서, 해결방법을 찾아보니

    web.xml에
    <filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    이렇게 설정하는 방법이 있더군요. 이렇게 하면 OpenEntityManagerInViewFilter가
    알아서 EntityManager (Session) 문제를 해결해 주는거 같습니다.

    이문제가 스프링 포럼에서도 자주 거론되는거 같은데, 너무 대충 찾아봐서 그런지
    이얘기 저얘기 많기만 하고 아주 명확한 답은 안 보이네요.
    사실 너무 대충 찾아봐서 제가 놓쳤을 가능성이 큽니다만...
    Spring document에서는 이문제에 대한 방법이나 해결방법은 못본거 같은데...

    저런 방법들 보다 훨씬 좋은 해결법이 있는지 나중에 토비님께 한번 여쭤봐야겠네요...^^;

    Favicon of http://whiteship.tistory.com BlogIcon 2009.08.08 10:21 PERM MOD/DEL

    네. 이번에 발생한 상황과 비슷한 상황이네요.

    스프링 시큐리티에서 관리중인 member 객체를 가져왔더니.. 그 녀석은 이미 하이버네이트 세션 컨텍스트에서 나가버린지 한 참 된 detached 상태의 객체였던 거죠.

    그런 녀석한테 lazy loading으로 컬렉션을 가져오라고 시켰으니... 세션이 없다는 에러가 나올법도 하지만.. 조금 만 더 직관적으로 detached 객체라는 사실을 알려줬으면 하는 바램이에요. ㅠ.ㅠ

  2. Favicon of http://toby.epril.com BlogIcon 2009.08.08 05:57 PERM. MOD/DEL REPLY

    씸을 쓰면 detached가 아예 없이 하이버 개발이 가능하니 바꺼. 저런 문제 전혀 신경 안써도 댄다고.

    아니면 HIA2에 나오는 contextual session 방식을 쓰덩가. seam이 엄다면 쩜 복잡하긴 하지만.

    Favicon of http://whiteship.tistory.com BlogIcon 2009.08.08 10:23 PERM MOD/DEL

    씸은 다음 기회에~
    contextual session은 한 번 고려해보죠.

  3. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.08.12 23:04 PERM. MOD/DEL REPLY

    헐... 이제 Seam까지 써야 하나요? @_@;;;
    이거, 본 application보다 frameworks만 자꾸 늘어나는 느낌이... 덜덜덜...
    암튼 처음에 저 문제 때문에 짜증나서, DB쪽은 그냥 Spring 쓰지 말고,
    ThreadLocal 로 만들어서 쓸까? 하는 생각도 했습니다.

    기선 2009.08.13 09:05 PERM MOD/DEL

    ㅎㅎ그러게요.
    씸도 공부해보고 싶어요.
    톱님이 하두 좋다고 하시니.. 더 궁금해져요.

Write a comment.




: 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : ··· : 11 :