Get vs Merge queries ... Not the same?
(Hibernate Debugging Continued ...)
As explained in Persist vs Merge page, Hibernate has an API:
Session.merge(Object)
This loads the object from the database and then merges its state into the current object.
For doing so, one would expect the merge() to generate same queries as get() .
However, this is generally not the case and is usually a cause of confusion.
Consider the stack trace obtained for each of the cases:
SqlStatementLogger.logStatement() line: 94
StatementPreparerImpl.prepareQueryStatement() line: 160
EntityLoader(AbstractLoadPlanBasedLoader).prepareQueryStatement() line: 257
EntityLoader(AbstractLoadPlanBasedLoader).executeQueryStatement() line: 201
EntityLoader(AbstractLoadPlanBasedLoader).executeload() line: 137
EntityLoader(AbstractLoadPlanBasedLoader).executeload() line: 102
EntityLoader(AbstractLoadPlanBasedEntityLoader).load() line: 186
SingleTableEntityPersister(AbstractEntityPersister).load() line: 4120
DefaultLoadEventListener.loadFromDatasource() line: 502
DefaultLoadEventListener.doload() line: 467
DefaultLoadEventListener.load() line: 212
DefaultLoadEventListener.proxyOrload() line: 274
DefaultLoadEventListener.onload() line: 150
SessionImpl.fireload() line: 1066
SessionImpl.access$2000() line: 176
SessionImpl$IdentifierLoadAccessImpl.load() line: 2540
SessionImpl.get(Class, Serializable) line: 951
EntityManagerImpl(AbstractEntityManagerImpl).find() line: 1110
EntityManagerImpl(AbstractEntityManagerImpl).find() line: 1068
SqlStatementLogger.logStatement() line: 94
StatementPreparerImpl.prepareQueryStatement() line: 160
CascadeEntityLoader(Loader).prepareQueryStatement() line: 1884
CascadeEntityLoader(Loader).executeQueryStatement() line: 1861
CascadeEntityLoader(Loader).executeQueryStatement() line: 1838
CascadeEntityLoader(Loader).doQuery() line: 909
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections() line: 354
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections() line: 324
CascadeEntityLoader(Loader).loadEntity() line: 2146
CascadeEntityLoader(AbstractEntityLoader).load() line: 78
CascadeEntityLoader(AbstractEntityLoader).load() line: 68
SingleTableEntityPersister(AbstractEntityPersister).load() line: 4120
DefaultLoadEventListener.loadFromDatasource() line: 502
DefaultLoadEventListener.doload() line: 467
DefaultLoadEventListener.load() line: 212
DefaultLoadEventListener.proxyOrload() line: 274
DefaultLoadEventListener.onload() line: 150
SessionImpl.fireload() line: 1066
SessionImpl.access$2000() line: 176
SessionImpl$IdentifierLoadAccessImpl.load() line: 2540
SessionImpl.get(String, Serializable) line: 956
DefaultMergeEventListener.entityIsDetached(MergeEvent, Map) line: 271
DefaultMergeEventListener.onMerge(MergeEvent, Map) line: 151
DefaultMergeEventListener.onMerge(MergeEvent) line: 76
SessionImpl.fireMerge(MergeEvent) line: 872
SessionImpl.merge(String, Object) line: 854
SessionImpl.merge(Object) line: 859
* All code here is from Hibernate version 4.3.1.Final
Reading the stack traces from bottom to up, it can be seen that both get() and merge() call same function on #21:
SessionImpl$IdentifierLoadAccessImpl.load() line: 2540
But they both digress at #13
This is because of the following function:
(AbstractEntityPersister.java , line 4119)
public Object load(
Serializable id, Object optionalObject,
LockOptions lockOptions, SessionImplementor session)
{
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) );
}
final UniqueEntityLoader loader = getAppropriateLoader(lockOptions, session );
return loader.load( id, optionalObject, session, lockOptions );
}
Hibernate populates all the EntityLoaders in a HashMap as:
(AbstractEntityPersister.java , line 4027)
protected void createLoaders()
{
final Map loaders = getLoaders();
loaders.put( LockMode.NONE, createEntityLoader( LockMode.NONE ) );
loaders.put( LockMode.OPTIMISTIC, createEntityLoader( LockMode.OPTIMISTIC) );
//... Similar code for all other LockModes
// Only for merge/refresh, put a different EntityLoader
loaders.put( "merge", new CascadeEntityLoader( this, CascadingActions.MERGE, getFactory()));
loaders.put( "refresh", new CascadeEntityLoader( this, CascadingActions.REFRESH, getFactory()));
}
So the EntityLoader selected for get() is different from that of merge() and since both of
them handle entity-loading differently, they fire different queries as well.
The CustomEntityLoader fires extra queries to load non-lazy collections as shown by the following stack trace:
CollectionLoader(AbstractLoadPlanBasedLoader).executeQueryStatement() line: 201
CollectionLoader(AbstractLoadPlanBasedLoader).executeload() line: 137
CollectionLoader(AbstractLoadPlanBasedLoader).executeload() line: 102
CollectionLoader(AbstractLoadPlanBasedCollectionInitializer).initialize() line: 100
OneToManyPersister(AbstractCollectionPersister).initialize() line: 693
DefaultInitializeCollectionEventListener.onInitializeCollection() line: 92
SessionImpl.initializeCollection() line: 1893
PersistentMap(AbstractPersistentCollection).forceInitialization() line: 668
StatefulPersistenceContext.initializeNonLazyCollections() line: 885
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections() line: 359
CascadeEntityLoader(Loader).doQueryAndInitializeNonLazyCollections() line: 324
CascadeEntityLoader(Loader).loadEntity() line: 2146
CascadeEntityLoader(AbstractEntityLoader).load() line: 78
CascadeEntityLoader(AbstractEntityLoader).load() line: 68
SingleTableEntityPersister(AbstractEntityPersister).load() line: 4120
DefaultLoadEventListener.loadFromDatasource() line: 502