|
Steve,
I would love to contribute to this project, however, I think I have beat my head on the tooling stuff way too long. I could be half way to the Component two phase loading solution in the time I have wasted so far. This project is not compatable with Eclipse at all, the comments on the Contributing to Hibernate using Eclipse and my experience bare witness to that.
What environment are you using?
Anyway, I will pass on my approach....
So my understanding of the infinite loop is this (My test case is a little more complicated, so I will try to simplify).... We have 4 objects: 1) Parent (who has a Set of EAGERly fetched ChildRlationship objects - one-to-many) 2) ParentChildRelationship objects (that have a many-to-one reference to the Parent and a many-to-one reference to the Child, thus the circular relationship) Note: The ParentChildRelationship has a composite Key of the Parent and Child, and an additional relationshipFlavor field. 3) ParentChildRelationship Id (the @IdClass object for ParentChildRelationship with a composite Key of the Parent and Child) 4) The Child object that does not need to reference anything.
The Scenario: We load the Parent object with a 'left join fetch' to the ParentChildRelationship (table) and a 'left join fetch' to the Child (table). So, in the Loader, when we hit extractKeysFromResultSet(...) (Loader line 760) we have at least three keys to build. 1) The Parent key, a LongType 2) The ParentChildRelationship key, a ComponentType referencing a) ManyToOneType referencing, a LongType (the Parent) b) ManyToOneType referencing, a LongType (the Child) 3) The Child key, a LongType
The problem: The problem comes when we try to resolve the keys (Loader line 839), 1) The parent key, no problem, resolves fine. 2) The ComponentType for the ParentChildRelationship. This tries to resolve and, as part of the composite resolve logic, we try to create a reference to the Parent object (which is actually part of the value of the key). The Parent object is not been added to the cache, so it hits the data source, resulting in roughly the original query, because the set is an EAGER fetch, and we call back into the extractKeysFromResultSet(...) with the same 3 keys to build again.... Infinite Loop I reproduced it in the debugger with this stack....
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1070
SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 989
ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 716
ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 502
EmbeddedComponentType(ComponentType).resolve(Object, SessionImplementor, Object) line: 666
CascadeEntityLoader(Loader).extractKeysFromResultSet(Loadable[], QueryParameters, ResultSet, SessionImplementor, EntityKey[], LockMode[], List) line: 838
CascadeEntityLoader(Loader).getRowFromResultSet(ResultSet, SessionImplementor, QueryParameters, LockMode[], EntityKey, List, EntityKey[], boolean, ResultTransformer) line: 720
CascadeEntityLoader(Loader).processResultSet(ResultSet, QueryParameters, SessionImplementor, boolean, ResultTransformer, int, List<AfterLoadAction>) line: 952
CascadeEntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 920
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 354
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 324
CascadeEntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2148
CascadeEntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 78
CascadeEntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 68
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 4126
DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 503
DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 468
DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 213
DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 275
DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 151
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1070
SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 989
ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 716
ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 502
EmbeddedComponentType(ComponentType).resolve(Object, SessionImplementor, Object) line: 666
CascadeEntityLoader(Loader).extractKeysFromResultSet(Loadable[], QueryParameters, ResultSet, SessionImplementor, EntityKey[], LockMode[], List) line: 838
CascadeEntityLoader(Loader).getRowFromResultSet(ResultSet, SessionImplementor, QueryParameters, LockMode[], EntityKey, List, EntityKey[], boolean, ResultTransformer) line: 720
CascadeEntityLoader(Loader).processResultSet(ResultSet, QueryParameters, SessionImplementor, boolean, ResultTransformer, int, List<AfterLoadAction>) line: 952
CascadeEntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 920
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean, ResultTransformer) line: 354
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 324
CascadeEntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2148
CascadeEntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 78
CascadeEntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 68
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 4126
DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 503
DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 468
DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 213
DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 275
DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 151
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1070
SessionImpl.access$2000(SessionImpl, LoadEvent, LoadEventListener$LoadType) line: 176
SessionImpl$IdentifierLoadAccessImpl.load(Serializable) line: 2551
SessionImpl.get(String, Serializable) line: 960
JpaMergeEventListener(DefaultMergeEventListener).entityIsDetached(MergeEvent, Map) line: 306
JpaMergeEventListener(DefaultMergeEventListener).onMerge(MergeEvent, Map) line: 186
JpaMergeEventListener(DefaultMergeEventListener).onMerge(MergeEvent) line: 85
SessionImpl.fireMerge(MergeEvent) line: 876
SessionImpl.merge(String, Object) line: 858
SessionImpl.merge(Object) line: 863
EntityManagerImpl(AbstractEntityManagerImpl).merge(A) line: 1196
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 483
SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(Object, Method, Object[]) line: 289
$Proxy51.merge(Object) line: not available
SimpleJpaRepository<T,ID>.save(S) line: 396
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 483
RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(Object, Method, Object[]) line: 442
RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(MethodInvocation) line: 427
RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(MethodInvocation) line: 381
ReflectiveMethodInvocation.proceed() line: 179
RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(MethodInvocation) line: 512
ReflectiveMethodInvocation.proceed() line: 179
TransactionInterceptor$1.proceedWithInvocation() line: 98
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class<?>, InvocationCallback) line: 266
TransactionInterceptor.invoke(MethodInvocation) line: 95
ReflectiveMethodInvocation.proceed() line: 179
PersistenceExceptionTranslationInterceptor.invoke(MethodInvocation) line: 136
ReflectiveMethodInvocation.proceed() line: 179
CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(MethodInvocation) line: 111
ReflectiveMethodInvocation.proceed() line: 179
ExposeInvocationInterceptor.invoke(MethodInvocation) line: 92
ReflectiveMethodInvocation.proceed() line: 179
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 207
$Proxy60.save(Object) line: not available
AccountCreateTest.test() line: 86
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 483
FrameworkMethod$1.runReflectiveCall() line: 47
FrameworkMethod$1(ReflectiveCallable).run() line: 12
FrameworkMethod.invokeExplosively(Object, Object...) line: 44
InvokeMethod.evaluate() line: 17
RunBefores.evaluate() line: 26
BlockJUnit4ClassRunner(ParentRunner<T>).runLeaf(Statement, Description, RunNotifier) line: 271
BlockJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 70
BlockJUnit4ClassRunner.runChild(Object, RunNotifier) line: 50
ParentRunner$3.run() line: 238
ParentRunner$1.schedule(Runnable) line: 63
BlockJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 236
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 53
ParentRunner$2.evaluate() line: 229
BlockJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 309
JUnit4TestClassReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38
RemoteTestRunner.runTests(String[], String, TestExecution) line: 459
RemoteTestRunner.runTests(TestExecution) line: 675
RemoteTestRunner.run() line: 382
RemoteTestRunner.main(String[]) line: 192
The good: This approach is great in that we do one call to the DB to get the results. The bad: We cannot resolve the Component Key i.e.. infinate loop
The Solution: 1) Long term, I think you are correct that a 2 phase load for Components is the right way to go. 2) Short term, If we detect the case where we are loading a Component (Composite Key) and the leaf level type that it reference (in this case the Parent's LongType), then defer the IdType.resolve(), and let Hibernate resolve it later with a second call to the DB. In this case, we are making more calls than needed, however it is simpler to implement than a 2 phase load for the Components. With only minor changes to extractKeysFromResultSet(), and the addition of two methods to detect the infinate loop condition.
The down side to solution #2 is that you make another hit on the database for the ParentChildRelationship when resolving the Component.
I have code for solution #2, and it works for all of my unit tests, however I am unable to verify it against the hibernate test suite, due to tooling issues .
I will be glad to share my code if you would like to look at it..... Let me know...
Thanks,
Jeff
|