[
http://opensource.atlassian.com/projects/hibernate/browse/HHH-3220?page=c...
]
Chris Wilson updated HHH-3220:
------------------------------
Attachment: HibernateStatelessSessionCriteriaAssertionFailureTest.java
This is a test for HHH-3220, which currently prevents us from using StatelessSession to
retrieve large numbers of objects for synchronization without loading them into the
Hibernate cache, which would eventually clog the cache and exhaust the available memory.
The problem is more-or-less as described in the ticket, but it's difficult to
reproduce because it only occurs when the SQL generated for the (criteria or HQL) query
omits a join to an associated table, because that join exceeds the maximum query depth.
Therefore it normally requires a deep structure of classes to reproduce. This test forces
the maximum query depth down to 1, to make it easier to reproduce.
TwoPhaseLoad.initializeEntity tries to resolve the identifier of the associated object of
a ManyToOneType association:
{code}
Type[] types = persister.getPropertyTypes();
for ( int i = 0; i < hydratedState.length; i++ ) {
final Object value = hydratedState[i];
if ( value!=LazyPropertyInitializer.UNFETCHED_PROPERTY &&
value!=BackrefPropertyAccessor.UNKNOWN ) {
hydratedState[i] = types[i].resolve( value, session, entity );
}
}
{code}
ManyToOneType's resolve() method calls resolveIdentifier(), which calls the
Session's internalLoad() method.
The identified object does not exist in the cache, because it was not loaded because its
depth exceeded the maximum join depth. Therefore StatelessSessionImpl tries to load it by
calling get():
{code}
EntityPersister persister = getFactory().getEntityPersister( entityName );
// first, try to load it from the temp PC associated to this SS
Object loaded = temporaryPersistenceContext.getEntity( new EntityKey( id, persister,
getEntityMode() ) );
if ( loaded != null ) {
// we found it in the temp PC. Should indicate we are in the midst of processing a
result set
// containing eager fetches via join fetch
return loaded;
}
if ( !eager && persister.hasProxy() ) {
// if the metadata allowed proxy creation and caller did not request forceful eager
loading,
// generate a proxy
return persister.createProxy( id, this );
}
// otherwise immediately materialize it
return get( entityName, id );
{code}
Which clears the entire persistence context:
{code}
public Object get(String entityName, Serializable id, LockMode lockMode) {
errorIfClosed();
Object result = getFactory().getEntityPersister(entityName)
.load(id, null, lockMode, this);
temporaryPersistenceContext.clear();
return result;
}
{code}
Which means that the next attempt to initialize a hydrated object does not find the
(parent) object in the persistence cache:
{code}
for ( int i = 0; i < hydratedObjectsSize; i++ ) {
TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
}
{code}
which results in the assertion failure:
{code}
org.hibernate.AssertionFailure: possible non-threadsafe access to the session
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:123)
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.HibernateStatelessSessionCriteriaAssertionFailureTest.assertList(HibernateStatelessSessionCriteriaAssertionFailureTest.java:721)
at
org.wfp.rita.test.hibernate.HibernateStatelessSessionCriteriaAssertionFailureTest.testFailing(HibernateStatelessSessionCriteriaAssertionFailureTest.java:743)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.wfp.rita.test.base.HibernateTestBase.runTestMethod(HibernateTestBase.java:204)
at org.wfp.rita.test.base.HibernateTestBase.runTest(HibernateTestBase.java:117)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:230)
at junit.framework.TestSuite.run(TestSuite.java:225)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
{code}
One workaround is to increase the maximum fetch depth until the error no longer occurs, by
setting the hibernate.max_fetch_depth property to a large value. However, this may have
side-effects if you have badly-behaved queries. So I recommend modifying
StatelessSessionImpl.internalLoad(), so that rather than call the public get() method
which has the side-effect of clearing the cache, it simply retrieves the object directly
without clearing the cache:
{code}
// otherwise immediately materialize it
return getFactory().getEntityPersister(entityName).load(id,
null, LockMode.NONE, this);
{code}
Anyone who needs a solution to this problem and can't run a patched version of
Hibernate can wrap the SessionWrapper class from the attached test case around their
StatelessSession to easily apply this workaround.
Patch to prevent "org.hibernate.AssertionFailure: possible
non-threadsafe access to the session" error caused by stateless sessions
-----------------------------------------------------------------------------------------------------------------------------------
Key: HHH-3220
URL:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-3220
Project: Hibernate Core
Issue Type: Patch
Components: core
Affects Versions: 3.2.6
Environment: Hibernate 3.2.6, Apache Derby on Mac OSX & PC
Reporter: Dan Bisalputra
Priority: Minor
Attachments: HibernateStatelessSessionCriteriaAssertionFailureTest.java,
StatelessSession-patch
When performing a query in a stateless session, the query loads objects in a two-phase
process in which a temporary persistence context is populated with empty objects in the
first phase, then the objects' member data are read from the database in the second
phase. If one of the objects contains an association or a collection, it performs a
recursive call to the session's get() method. The get() method clears the temporary
persistence context, so if the parent object contains any other associations to be read in
the second phase, Hibernate throws an assertion because they are not found in the
persistence context.
This patch solves the problem by only clearing the persistence context when the recursion
ends. It passes all the unit tests for our application, but I have not tested it with any
of the Hibernate unit tests.
--
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