Community
Participate
Working Groups
Build ID: 1.0.1 Steps To Reproduce: Execute a query during @PrePersist with flushmode set to the default value (auto). Newsgroups, topic "@PrePersist endless loop", James' posting 2008-11-19 14:43 "Please also log a bug for the loop, if we are in the process of a flush, we should not attempt another flush, although if the query's flush mode is auto, we will have to throw an error" More information: When an entity is persisted some other entities must be created. Using the @PrePersist annotation and then start creating and persisting new entities. This results in an endless loop. Thread [AWT-EventQueue-1] (Suspended (breakpoint at line 78 in Batchtransferline)) Batchtransferline.preSaveHook() line: 78 Batchtransferline(AbstractBean).preSave() line: 230 GeneratedMethodAccessor81.invoke(Object, Object[]) line: not available DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 Method.invoke(Object, Object...) line: 597 PrivilegedAccessHelper.invokeMethod(Method, Object, Object[]) line: 344 EntityClassListener(EntityListener).invokeMethod(Method, Object, Object[], DescriptorEvent) line: 297 EntityClassListener.invokeMethod(String, DescriptorEvent) line: 64 EntityClassListener(EntityListener).preUpdateWithChanges(DescriptorEvent) line: 426 DescriptorEventManager.notifyListener(DescriptorEventListener, DescriptorEvent) line: 656 DescriptorEventManager.notifyEJB30Listeners(DescriptorEvent) line: 593 DescriptorEventManager.executeEvent(DescriptorEvent) line: 187 ExpressionQueryMechanism(DatabaseQueryMechanism).updateObjectForWriteWithChangeSet() line: 1083 UpdateObjectQuery.executeCommitWithChangeSet() line: 84 ExpressionQueryMechanism(DatabaseQueryMechanism).executeWriteWithChangeSet() line: 290 UpdateObjectQuery(WriteObjectQuery).executeDatabaseQuery() line: 58 UpdateObjectQuery(DatabaseQuery).execute(AbstractSession, AbstractRecord) line: 666 UpdateObjectQuery(DatabaseQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 585 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWorkObjectLevelModifyQuery(UnitOfWorkImpl, AbstractRecord) line: 114 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 86 RepeatableWriteUnitOfWork(UnitOfWorkImpl).internalExecuteQuery(DatabaseQuery, AbstractRecord) line: 2587 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord, int) line: 1178 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord) line: 1162 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery) line: 1108 CommitManager.commitChangedObjectsForClassWithChangeSet(UnitOfWorkChangeSet, String) line: 297 CommitManager.commitAllObjectsForClassWithChangeSet(UnitOfWorkChangeSet, Class) line: 228 CommitManager.commitAllObjectsWithChangeSet(UnitOfWorkChangeSet) line: 185 RepeatableWriteUnitOfWork(AbstractSession).writeAllObjectsWithChangeSet(UnitOfWorkChangeSet) line: 3129 RepeatableWriteUnitOfWork(UnitOfWorkImpl).commitToDatabase(boolean) line: 1241 RepeatableWriteUnitOfWork.commitToDatabase(boolean) line: 478 RepeatableWriteUnitOfWork(UnitOfWorkImpl).commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkChangeSet, boolean) line: 1370 RepeatableWriteUnitOfWork.writeChanges() line: 300 EntityManagerImpl.flush() line: 384 EJBQueryImpl.performPreQueryFlush() line: 936 EJBQueryImpl.executeReadQuery() line: 378 EJBQueryImpl.getResultList() line: 517 Batch.findAllThatHaveAmountLeftOrderByBatchnr(Batchtype) line: 103 Batchcount.changeAllocation(BigInteger, Batchtransferline) line: 85 Batchtransferline.preSaveHook() line: 86 Batchtransferline(AbstractBean).preSave() line: 230 GeneratedMethodAccessor81.invoke(Object, Object[]) line: not available DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 Method.invoke(Object, Object...) line: 597 PrivilegedAccessHelper.invokeMethod(Method, Object, Object[]) line: 344 EntityClassListener(EntityListener).invokeMethod(Method, Object, Object[], DescriptorEvent) line: 297 EntityClassListener.invokeMethod(String, DescriptorEvent) line: 64 EntityClassListener(EntityListener).preUpdateWithChanges(DescriptorEvent) line: 426 DescriptorEventManager.notifyListener(DescriptorEventListener, DescriptorEvent) line: 656 DescriptorEventManager.notifyEJB30Listeners(DescriptorEvent) line: 593 DescriptorEventManager.executeEvent(DescriptorEvent) line: 187 ExpressionQueryMechanism(DatabaseQueryMechanism).updateObjectForWriteWithChangeSet() line: 1083 UpdateObjectQuery.executeCommitWithChangeSet() line: 84 ExpressionQueryMechanism(DatabaseQueryMechanism).executeWriteWithChangeSet() line: 290 UpdateObjectQuery(WriteObjectQuery).executeDatabaseQuery() line: 58 UpdateObjectQuery(DatabaseQuery).execute(AbstractSession, AbstractRecord) line: 666 UpdateObjectQuery(DatabaseQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 585 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWorkObjectLevelModifyQuery(UnitOfWorkImpl, AbstractRecord) line: 114 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 86 RepeatableWriteUnitOfWork(UnitOfWorkImpl).internalExecuteQuery(DatabaseQuery, AbstractRecord) line: 2587 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord, int) line: 1178 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord) line: 1162 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery) line: 1108 CommitManager.commitChangedObjectsForClassWithChangeSet(UnitOfWorkChangeSet, String) line: 297 CommitManager.commitAllObjectsForClassWithChangeSet(UnitOfWorkChangeSet, Class) line: 228 CommitManager.commitAllObjectsWithChangeSet(UnitOfWorkChangeSet) line: 185 RepeatableWriteUnitOfWork(AbstractSession).writeAllObjectsWithChangeSet(UnitOfWorkChangeSet) line: 3129 RepeatableWriteUnitOfWork(UnitOfWorkImpl).commitToDatabase(boolean) line: 1241 RepeatableWriteUnitOfWork.commitToDatabase(boolean) line: 478 RepeatableWriteUnitOfWork(UnitOfWorkImpl).commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkChangeSet, boolean) line: 1370 RepeatableWriteUnitOfWork.writeChanges() line: 300 EntityManagerImpl.flush() line: 384 EJBQueryImpl.performPreQueryFlush() line: 936 EJBQueryImpl.executeReadQuery() line: 378 EJBQueryImpl.getResultList() line: 517 Batch.findAllThatHaveAmountLeftOrderByBatchnr(Batchtype) line: 103 Batchcount.changeAllocation(BigInteger, Batchtransferline) line: 85 Batchtransferline.preSaveHook() line: 86 ... Batchtransferline.preSaveHook() line: 86 Batchtransferline(AbstractBean).preSave() line: 230 GeneratedMethodAccessor81.invoke(Object, Object[]) line: not available DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 Method.invoke(Object, Object...) line: 597 PrivilegedAccessHelper.invokeMethod(Method, Object, Object[]) line: 344 EntityClassListener(EntityListener).invokeMethod(Method, Object, Object[], DescriptorEvent) line: 297 EntityClassListener.invokeMethod(String, DescriptorEvent) line: 64 EntityClassListener(EntityListener).preUpdateWithChanges(DescriptorEvent) line: 426 DescriptorEventManager.notifyListener(DescriptorEventListener, DescriptorEvent) line: 656 DescriptorEventManager.notifyEJB30Listeners(DescriptorEvent) line: 593 DescriptorEventManager.executeEvent(DescriptorEvent) line: 187 ExpressionQueryMechanism(DatabaseQueryMechanism).updateObjectForWriteWithChangeSet() line: 1083 UpdateObjectQuery.executeCommitWithChangeSet() line: 84 ExpressionQueryMechanism(DatabaseQueryMechanism).executeWriteWithChangeSet() line: 290 UpdateObjectQuery(WriteObjectQuery).executeDatabaseQuery() line: 58 UpdateObjectQuery(DatabaseQuery).execute(AbstractSession, AbstractRecord) line: 666 UpdateObjectQuery(DatabaseQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 585 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWorkObjectLevelModifyQuery(UnitOfWorkImpl, AbstractRecord) line: 114 UpdateObjectQuery(ObjectLevelModifyQuery).executeInUnitOfWork(UnitOfWorkImpl, AbstractRecord) line: 86 RepeatableWriteUnitOfWork(UnitOfWorkImpl).internalExecuteQuery(DatabaseQuery, AbstractRecord) line: 2587 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord, int) line: 1178 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery, AbstractRecord) line: 1162 RepeatableWriteUnitOfWork(AbstractSession).executeQuery(DatabaseQuery) line: 1108 CommitManager.commitChangedObjectsForClassWithChangeSet(UnitOfWorkChangeSet, String) line: 297 CommitManager.commitAllObj
I have reproduced this error using a nested persist() inside the @PrePersist callback method of the same entity being secondarily persisted. The following prerequisites are required for reproduction. - Run tests out of container on an SE persitence unit - At least two entities must be persisted to avoid performance optimization code for a single entity - Two different entity classes are required - Test must commit more than one object so that the non-performance else clause in CommitManager.commitAllObjectsWithChangeSet() is used - Test must perform a change on an existing object that is already persisted - Set FlushMode via entityManager.setFlushMode(FlushModeType.AUTO); - Set a @PrePersist method callback on an Entity that does not perform a secondary persist - Set a @PrePersist method callback on an Entity that performs a secondary persist Analysis document with reproduction, usecases and design details is being written to the following location. http://wiki.eclipse.org/EclipseLink/Bugs/256277
Created attachment 121567 [details] UC1 - reproduction SE
Created attachment 121568 [details] UC2 - SE JPA example (no infinite loop - PrePersist persist entity does not have its own persist)
The core of this issue is the automatic flushing that occurs when a query is executed against an em. em.commit()-> em.flush() -> prePersist event -> em.query -> em.flush()-> prePersist() EclipseLink executes a flush on query execution to ensure that the query resolves against any uncommitted data.
The EM should track if it is already flushing and if it is then a query execution logs a warning (like: "EM is already flushing. Query will be executed without further changes being written to database. If the query is conditional upon changed data the changes may not be reflected in the results. Users should issue a flush() call upon completion of the dependent changes and prior to this flush() to ensure correct results") and does not execute a subsequent flush().
To reproduce the Entity on which the prePersist exists should be an unregistered new object and be referenced by a managed Entity.
Created attachment 122220 [details] UC1 - reproduction SE code (nested flush() caused by read query on PrePersist) See data model at http://wiki.eclipse.org/EclipseLink/Bugs/256277#Data_Model
Created attachment 122222 [details] eclipselink.jpa patch (pre-code-review) patch based on svn ref 3162 jpa lrg run with 1064,1,0 (expected existing 1 PostLoad failure)
Created attachment 122453 [details] eclipselink core patch (code review 1) Code-reviewed by Tom and Gordon
Created attachment 122455 [details] eclipselink core patch (code review 1) - missed saving RWUOW Created patch without saving a file in eclipse - ignore previously commented // return;
Created attachment 122469 [details] eclipselink core patch (code review 1) - moved set for isWithinFlush within try/catch I moved the place where the flag was set so that it is inside the try/catch block. We are then always setting the flag back to false in the finally block when we come back from a writeChanges() call that would have been nested. There were 17 flush() failures on the JPA LRG with my previous patch because I was returning out before the try block with the flag set to true in some use cases. Currently with this patch we match a clean view: JPA LRG: (1 expected PostClone failure) [junit] Tests run: 1065, Failures: 1, Errors: 0, Time elapsed: 806.286 sec Foundation LRG: (2 expected getContainerAttributeName XML failures) [junit] Tests run: 5626, Failures: 0, Errors: 2, Time elapsed: 694.761 sec
Created attachment 122486 [details] eclipselink core patch (code review 1) - same as original patch but with 2 flag resets (in with/without commit changes) One more iteration, I sent the mail too soon and it turned out that I was setting the flag too late for my reproduction - which occurs in calculateChanges. So we now set the flag before calculateChanges like before but clear it in two places instead of one. - when we don't commit anything on an early return - just before the return - when we commit changes - in the finally block Details: ---------- The call to calculateChanges() can be recursive before we even get to commitToDatabaseWithPreBuiltChangeSet(). In this case we will commit in a PrePersist and then later fail on a second commit. We therefore need to move the flag set before the try/catch block. Thread [main] (Suspended (breakpoint at line 293 in RepeatableWriteUnitOfWork)) RepeatableWriteUnitOfWork.writeChanges() line: 293 EntityManagerImpl.flush() line: 534 EJBQueryImpl.performPreQueryFlush() line: 1038 EJBQueryImpl.executeReadQuery() line: 347 EJBQueryImpl.getResultList() line: 592 Address.processQuery(EntityManager) line: 83 Address.prePersist() line: 73 NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method] NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39 DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 Method.invoke(Object, Object...) line: 597 PrivilegedAccessHelper.invokeMethod(Method, Object, Object[]) line: 344 EntityClassListener(EntityListener).invokeMethod(Method, Object, Object[], DescriptorEvent) line: 297 EntityClassListener.invokeMethod(String, DescriptorEvent) line: 64 EntityClassListener(EntityListener).prePersist(DescriptorEvent) line: 412 DescriptorEventManager.notifyListener(DescriptorEventListener, DescriptorEvent) line: 650 DescriptorEventManager.notifyEJB30Listeners(DescriptorEvent) line: 593 DescriptorEventManager.executeEvent(DescriptorEvent) line: 187 RepeatableWriteUnitOfWork(UnitOfWorkImpl).registerNewObjectClone(Object, Object, ClassDescriptor) line: 3976 RepeatableWriteUnitOfWork(UnitOfWorkImpl).registerNotRegisteredNewObjectForPersist(Object, ClassDescriptor) line: 3955 RepeatableWriteUnitOfWork.registerNotRegisteredNewObjectForPersist(Object, ClassDescriptor) line: 356 RepeatableWriteUnitOfWork(UnitOfWorkImpl).discoverAndPersistUnregisteredNewObjects(Object, boolean, Map, Map, Map) line: 3857 OneToOneMapping(ObjectReferenceMapping).cascadeDiscoverAndPersistUnregisteredNewObjects(Object, Map, Map, Map, UnitOfWorkImpl) line: 713 ObjectBuilder.cascadeDiscoverAndPersistUnregisteredNewObjects(Object, Map, Map, Map, UnitOfWorkImpl) line: 1493 RepeatableWriteUnitOfWork(UnitOfWorkImpl).discoverAndPersistUnregisteredNewObjects(Object, boolean, Map, Map, Map) line: 3871 RepeatableWriteUnitOfWork.discoverUnregisteredNewObjects(Map, Map, Map, Map) line: 182 RepeatableWriteUnitOfWork(UnitOfWorkImpl).calculateChanges(Map, UnitOfWorkChangeSet, boolean) line: 613 RepeatableWriteUnitOfWork.writeChanges() line: 304 EntityManagerImpl.flush() line: 534 TestClient.prePersistQuery() line: 105 TestClient.main(String[]) line: 128 Sequence: flush() calls flush() because of uncommitted changes - flush - flag is set - calculateChanges called - flush 2 has no effect - changes committed - flag cleared in finally block Sequence: flush() calls flush() but no changes were uncommitted - flush - flag is set - calculateChanges called - flush 2 has no effect - changes not done - flag cleared before return - we return early before the commit Sequence: single flush() call with changes - flush - flag is set - calculateChanges called - changes committed - flag cleared in finally block Sequence: single flush() call with no changes - flush - flag is set - calculateChanges called - changes not done - flag cleared before return - we return early before the commit Test results: My reproduction and JPA LRG and Core LRG OK
The following patch has been reviewed and checked in for bug# 256277 http://bugs.eclipse.org/256277 https://bugs.eclipse.org/bugs/attachment.cgi?id=122486&action=diff http://fisheye2.atlassian.com/changelog/eclipselink/?cs=3202 Code-Review: Reviewed by Gordon and Tom - thank you Description: Currently flush() can be nested causing an infinite loop or DB errors on a double commit. We have introduced a boolean flag on RepeatableWriteUnitOfWork that is true while we are flushing and use this flag to stop any nested flushing from occurring. For a reproduction, we have a managed source entity that has some uncommitted changes. There is an unmanaged target entity that has a @PrePersist callback method that contains a query on the source . We do a final flush() or commit(). The result is that the @PrePersist method causes it's own flush() within a flush() - we need to disallow this. Originally I placed the logic to break out of a nested flush at the EJBQueryImpl.executeReadQuery() level, however after a code review this was moved to a more generic location in RepeatableWriteUnitOfWork.writeChanges(). Test Results: OK JPA LRG: (1 expected PostClone failure) [junit] Tests run: 1065, Failures: 1, Errors: 0, Time elapsed: 806.286 sec Foundation LRG: (2 expected getContainerAttributeName XML failures) [junit] Tests run: 5626, Failures: 0, Errors: 2, Time elapsed: 694.761 sec (reran under rev 3197) JPA LRG: [junit] Tests run: 1065, Failures: 1, Errors: 0, Time elapsed: 592.973 sec Core LRG: [junit] Tests run: 6646, Failures: 0, Errors: 0, Time elapsed: 882.218 sec SVN Merged into rev 3197 Created rev 3202
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink