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이 발생한다고 메시지를 출력해주면 좋겠다. 그랬다면... 좀 더 쉽게 해결할 수 있었을 법한 문제였다.