Hi there.
When you have versioned entity, the collections (associations) of the entity are not versioned themselves.
For example you have entity Route with collection of Nodes. Let's say the Route.node collection is lazy loaded.
With read/committed isolation level (standard for optimistic locking), you have this scenario:
with two transactions and a Route with id 1 and some Nodes on it:
t1 open transaction
t2 open transaction
t1.1 load a route id 1
t2.1 load a route id 1
t1.2 load route's nodes (invoke somethings, so that lazy load will proceed)
t1.3 delete all route's nodes
t1.4 commit
t2.2 load route's nodes (now, we have read/committed, so no nodes will be loaded)
t2.3 continue some code, that may fail that nodes are missing
t2.4 commit would probably fail on optimistic lock
Now. As you can see, in general, it should not be a problem. Anyway, the transaction t2 would fail on optimistic conflict when trying to update the route.
BUT, what concerns me, is the point t2.3. Where you have actually loaded a bad tree of the object Route. The Route itself is from another commit than it's collection of nodes. Unfortunately, we have some legacy code that can fail in such cases.
I would await, that Hibernate would fail on optimistic conflict when trying to load the lazy collection after read/committed and the Route is a versioned entity. Is this reasonable? Can this be proposed to hibernate team? Is this a bug? Can anyone say anything to this please? :-).
For time being, we have made a very simple fix for this. When collection is loaded in org.hibernate.loader.collection.CollectionLoader.java
Code:
...
public void initialize(Serializable id, SessionImplementor session)
throws HibernateException {
loadCollection( session, id, getKeyType() );
checkOwnerVersion(id, session);
}
private void checkOwnerVersion(Serializable id, SessionImplementor session)
{
Object colOwner = session.getPersistenceContext().getCollectionOwner(id, collectionPersister);
EntityEntry colOwnerEntry = session.getPersistenceContext().getEntry(colOwner);
EntityPersister ownerPersister = colOwnerEntry.getPersister();
Object version = colOwnerEntry.getVersion();
if (version != null) { //null version means the object is in the process of being loaded somewhere else in the ResultSet
VersionType versionType = ownerPersister.getVersionType();
Object currentVersion = ownerPersister.getCurrentVersion(colOwnerEntry.getId(), session);
if (!versionType.isEqual(version, currentVersion)) {
if (session.getFactory().getStatistics().isStatisticsEnabled()) {
session.getFactory().getStatisticsImplementor().optimisticFailure(ownerPersister.getEntityName());
}
throw new StaleObjectStateException(ownerPersister.getEntityName(), id);
}
}
}
...
In other words, after that collection is initialized, the version of it's owner is checked again. If the owner has changed, the collection was loaded after some read/committed has proceed and could be changed so that we would not notice. OptimisticFailure is thrown in such case.
Do you think it is for JIRA issue?
Thanx a lot!