4
votes

I got a really weird problem here and I absolutely cannot understand why this is happening.

The problem looks like this:

I got a class called "SmampiAccount" which holds a list of email accounts. The mapping file looks like this (shortened):

<hibernate-mapping>
    <class name="com.smampi.web.model.account.SmampiAccount" table="SMAMPIACCOUNT">
            <id name="id" type="long" access="field">
                    <column name="SMAMPI_ACCOUNT_ID" />
                    <generator class="native" />
            </id>

            <bag name="mailAccounts" table="MAILACCOUNTS" lazy="false" inverse="true">
                    <key column="SMAMPI_ACCOUNT_ID"></key>
                    <one-to-many class="com.smampi.web.model.mail.account.MailAccount"/>
            </bag>
    </class>
</hibernate-mapping>

I get instances of this class through this method:

public SmampiAccount loadSmampiAccount(long id) throws FailedDatabaseOperationException {

    SmampiAccount smampiAccount = null;
    Session session = null;
    Transaction transaction = null;
    try {
        session = getSession();
        transaction = session.beginTransaction();
        smampiAccount = (SmampiAccount) session.load(com.smampi.web.model.account.SmampiAccount.class, id);
        List<MailAccount> mailAccounts = smampiAccount.getMailAccounts();
        doSomething(mailAccounts);
        transaction.commit();
    } catch (Exception e) {
        rollback(transaction);
        closeSession();
        throw new FailedDatabaseOperationException(e);
    } finally {
        closeSession();
    }

    return smampiAccount;
}

private Session getSession() {
    if (_session == null) {
        _session = getSessionFactory().openSession();
    }
    if (_session.isOpen() == false) {
        _session = getSessionFactory().openSession();
    }
    return _session;
}

This works fine as it is.

Now, I wanted to add a new property to the mapping file in order to save a reference to a default email account:

<many-to-one name="defaultMailAccount" column="DEFAULT_MAIL_ACCOUNT_ID" />

Now, I get an exception in the method public SmampiAccount loadSmampiAccount(long id) in this line:

List<MailAccount> mailAccounts = smampiAccount.getMailAccounts();

Stacktrace:

org.hibernate.SessionException: Session is closed!
    at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:72)
    at org.hibernate.impl.SessionImpl.getPersistenceContext(SessionImpl.java:1954)
    at org.hibernate.event.def.DefaultPostLoadEventListener.onPostLoad(DefaultPostLoadEventListener.java:49)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:250)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:86)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:76)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3293)
    at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:496)
    at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:477)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:147)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
    at org.hibernate.impl.SessionImpl.immediateLoad(SessionImpl.java:1026)
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at com.smampi.web.model.account.SmampiAccount_$$_javassist_19.getMailAccounts(SmampiAccount_$$_javassist_19.java)

How is this possible? The session gets not closed manually and .commit() isn't called yet (which would normally close the session). It's also not possible that another method is interfering here because I create a new hibernate session for each method call which is dedicated just for this one method.


Edit

I added some debug info on the session open status:

session = getSession();

System.err.println(session.isOpen());
transaction = session.beginTransaction(); // 1 (true)

System.err.println(session.isOpen()); // 2 (true)
smampiAccount = (SmampiAccount) session.load(com.smampi.web.model.account.SmampiAccount.class, id);

System.err.println(session.isOpen()); // 3 (true)
List<MailAccount> mailAccounts = smampiAccount.getMailAccounts(); // Throws exception that session is closed
doSomething(mailAccounts);

System.err.println(session.isOpen()); // 4 (not called)
transaction.commit();

This gives me:

true
true
true
org.hibernate.SessionException: Session is closed!
1
It looks strange. I can suggest that you inspect the state of the session before and after the call to the load method (System.err.println(session.isOpen()); or something like that). Another question: if you manually close the session before load(), is the stack trace equivalent? - waxwing
Just for the purposes of testing, does it work fine if you add lazy="false" to the <many-to-one>? Also, which verison of Hibernate are you using? - matt b
@waxwing I added some debug info to my original post. The result looks very weird to me. I also tried to add a session.close() BEFORE the session.load(...) call. That again gives me a "Session is closed" exception. If I move the session.close() below the session.load(...) call, I get org.hibernate.LazyInitializationException: could not initialize proxy - no Session in this line: List<MailAccount> mailAccounts = smampiAccount.getMailAccounts(); - Timo Ernst
@matt Adding lazy="false" to <many-to-one> had no effect. I use Hibernate 3.6.3.Final with mysql-connector-java-3.0.10-stable and C3P0. - Timo Ernst
Just for sanity purposes, does the stacktrace point at the exact line of smampiAccount.getMailAccounts(); from your code sample? Are you using any other libraries in getting the Hibernate session, such as Spring, or any form of AOP? How about trying to upgrade Hibernate to a newer 3.6.x release, such as 3.6.5? Perhaps a bugfix addresses this. - matt b

1 Answers

2
votes

I am the biggest idiot in the world.

In the setter of defaultMailAccount, I had this:

public void setDefaultMailAccount(MailAccount defaultMailAccount) {
    this.defaultMailAccount = defaultMailAccount;
    try {
        databasecontroller.update(this);
    } catch (FailedDatabaseOperationException e) {
        handleException(e, false, null, null);
    }
}

The call to databasecontroller.update(this) caused a cascade whenever Hibernate tried to load a persisted version from the database and that again caused the session to close.

Moving the call of databasecontroller.update(..) to outside the method fixed the issue.

Sorry to everyone for taking up your time and thanks for the help!