Lazy collection initialization fails in hibernate
Asked Answered
S

1

8

Today I faced with next problem in hibernate:

My method:

@Transactional
public Period getDefault(Team team) {
    Period defaultPeriod = team.getDefaultPeriod();
    List<Period> periods = _periodDAO.getPeriods(team);
        if (!periods.contains(defaultPeriod)) {
            defaultPeriod = periods.get(periods.size() - 1);
        }
    }
    _periodDAO.initializeIssues(defaultPeriod);
    return defaultPeriod;
}

Method initializeIssues:

public void initializeIssues(Period period) {
    if (period.getIssues() != null) {
        Hibernate.initialize(period.getIssues());
    }
}

I receive exception if collection periods contains defaultPeriod

Caused by: org.hibernate.HibernateException: collection is not associated with any session
at org.hibernate.collection.AbstractPersistentCollection.forceInitialization(AbstractPersistentCollection.java:474)
at org.hibernate.Hibernate.initialize(Hibernate.java:417)

But if I remove some lines and change method to

@Transactional    
public Period getDefault(Team team) {
    Period defaultPeriod = team.getDefaultPeriod();
    _periodDAO.initializeIssues(defaultPeriod);
    return defaultPeriod;
}

It works fine.

I debugged first example and hibernate session does not close during whole method.

As I understand, if loaded object (one element in periods) in session has collection which associated with active session and existing before object (defaultPeriod) also has same association - it (defaultPeriod) will lose its association.

Is it truth? Who else faced with same problem?

Thank you for answers.

Stalagmite answered 10/9, 2013 at 16:55 Comment(0)
T
12

Presumably, your Team argument is coming from another transaction and another Hibernate Session.

When a @Transactional method returns, the TransactionManager closes the Session which does some cleanup and unsets (sets to null) the Session field of all PersistentCollection instances. Your defaultPeriod has one of these in its issues field.

Hibernate's Hibernate.initialize() forces the initialization of a lazy PersistentCollection, but has the following code (calls AbstractPersistentCollection#forceInitialization())

    if ( session == null ) {
        throw new HibernateException( "collection is not associated with any session" );
    }

If you are planning on using the issues collection outside the original @Transactional method (the code that produces Team), you need to load the underlying objects. Either change it to EAGER loading or do what you are doing with Hibernate.initialize().

Another solution is to make the Session last longer than just the length of the first @Transactional, but I don't have details for that. A quick google or SO search should bring up some options.


This is what is happening

Period defaultPeriod = team.getDefaultPeriod();

gets a Period object with id (ex.) 42. Because it happened in another Session that has since been closed, the issues is a PersistentCollection which has a null Session reference, and will throw the Exception you get.

The you do this

List<Period> periods = _periodDAO.getPeriods(team);

Let's say the List contains a Period object with id 42, so the if in

   if (!periods.contains(defaultPeriod)) {
        defaultPeriod = periods.get(periods.size() - 1);
   }

doesn't get executed. Although the equals() returns true (contains() also returns true and becomes false because of !), the objects are not the same. The on in the List has an attached (non-null) Session, so that one can be initialized. But yours, the one held by defaultPeriod cannot.

Torpor answered 10/9, 2013 at 16:59 Comment(5)
Thank you! But TransactionManager sets to null the Session field for issues in defaultPeriod only if periods contains defaultPeriod, if not - issues will initialize well.Stalagmite
@OleksandrZ I would take a look at your equals() method for comparing instances of Period. Although Periods might contain it, the one in the collection is attached to a Session, while the one on the outside is not.Torpor
equals method is very simple and based on comparing of Period id. I can initialize issues of defaultPeriod only if I don't retreive from db this entity again, so if I change code and add Period defaultPeriod2 = _periodDAO.get(defaultPeriod.getId()); instead of retrieving periods, issues will be not initialized again. Thanks.Stalagmite
Thank you! I missed @Transactional for another method getDefault in my question where I don't use _periodDAO. Sorry. So, as I understand, new transaction will be created only if I use query to db, not initialize existing collection.Stalagmite
Whatever happens inside a @Transactional will happen within a Hibernate Session, within a Transaction. When that method returns, depending on config, the Transaction will be committed and the Session will be closed. Initializing the collection can only happen if the Session is not closed yet. I don't think it necessarily has to be within the same Transaction.Torpor

© 2022 - 2024 — McMap. All rights reserved.