Criteria queries on lazily loaded composite (embedded) identifiers fail with
StatelessSession
---------------------------------------------------------------------------------------------
Key: HHH-4927
URL:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-4927
Project: Hibernate Core
Issue Type: Bug
Components: core
Affects Versions: 3.3.2
Reporter: Chris Wilson
Attachments: HibernateStatelessSessionCriteriaUniqueKeyJoinTest.java
When performing a org.hibernate.Criteria query in a stateless session, and a
PersistentClass links to another using a unique non-primary key reference, the referenced
object is not fully populated, only enough to store the linked primary key fields. This
makes Hibernate think that it's just loaded a transient object (as the ID is still
left null), which it doesn't like at all:
{noformat:title=Exception stack trace}
org.hibernate.TransientObjectException: object references an unsaved transient instance -
save the transient instance before flushing:
org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaUniqueKeyJoinTest$Project
at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:242)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:430)
at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:110)
at org.hibernate.type.ComponentType.nullSafeSet(ComponentType.java:307)
at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1732)
at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1703)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1593)
at org.hibernate.loader.Loader.doQuery(Loader.java:696)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadEntity(Loader.java:1885)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:71)
at org.hibernate.loader.entity.EntityLoader.loadByUniqueKey(EntityLoader.java:108)
at
org.hibernate.persister.entity.AbstractEntityPersister.loadByUniqueKey(AbstractEntityPersister.java:1662)
at org.hibernate.type.EntityType.loadByUniqueKey(EntityType.java:641)
at org.hibernate.type.EntityType.resolve(EntityType.java:415)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:139)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:877)
at org.hibernate.loader.Loader.doQuery(Loader.java:752)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.doList(Loader.java:2232)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2129)
at org.hibernate.loader.Loader.list(Loader.java:2124)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:118)
at org.hibernate.impl.StatelessSessionImpl.list(StatelessSessionImpl.java:565)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:306)
at
org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaUniqueKeyJoinTest.assertList(HibernateStatelessSessionCriteriaUniqueKeyJoinTest.java:162)
at
org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaUniqueKeyJoinTest.testFailingDefaultLazyStatelessSession(HibernateStatelessSessionCriteriaUniqueKeyJoinTest.java:218)
{noformat}
This call chain:
* org.hibernate.type.EntityType.resolve(EntityType.java:415)
* org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:139)
* org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:877)
will find the object ({@link Project} or {@link Site}) in the StatefulPersistenceContext
if it was eagerly loaded, because it has already been hydrated.
Otherwise, it calls EntityType.loadByUniqueKey() which ends up calling
ForeignKeys.getEntityIdentifierIfNotUnsaved(), which calls
SessionImplementor.getContextEntityIdentifier() to retrieve the Project or Site's ID,
to bind to the query to retrieve the ProjectSite.
If the {@link Session} is stateful, this works fine, because
SessionImpl.getContextEntityIdentifier() knows how to retrieve the identifier from the
HibernateProxy even though the object is not loaded yet.
However, StatelessSessionImpl has a lame implementation of getContextEntityIdentifier():
{code}
public Serializable getContextEntityIdentifier(Object object) {
errorIfClosed();
return null;
}
{code}
And because the object is a lazy proxy which has not yet been initialized, its fields are
all null, so ForeignKeys#isTransient returns true, and
ForeignKeys.getEntityIdentifierIfNotUnsaved() throws the exception shown above.
I think the best fix is to improve the implementation of
StatelessSessionImpl.getContextEntityIdentifier() to match the one in SessionImpl. We
could instead improve ForeignKeys.isTransient() to check for a proxy object, but I think
it makes more sense for there to be more shared code between SessionImpl and
StatelessSessionImpl instead. Ideally these should inherit from a common base class or the
statefulness should be extracted into a wrapper around a stateless Session instead.
The SessionWrapper class included in the attached test case is used by the included
testSuccessfulWorkaround() test to show that replacing the implementation of
StatelessSessionImpl.getContextEntityIdentifier() will fix the problem. It can also be
used to work around the problem without patching Hibernate, until the official fix is
released.
Change HibernateTestBase to org.hibernate.test.annotations.TestCase to run under
Hibernate.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
http://opensource.atlassian.com/projects/hibernate/secure/Administrators....
-
For more information on JIRA, see:
http://www.atlassian.com/software/jira