clean up code in listener implementations

- try to use smaller methods with fewer params
- git rid of early exits
- tiny fix for unloaded proxy deletion
This commit is contained in:
Gavin King 2022-09-27 14:40:40 +02:00
parent fcd7a45a75
commit 8de46167f6
16 changed files with 731 additions and 818 deletions

View File

@ -130,7 +130,6 @@ public abstract class AbstractSaveEventListener<C>
persister.getIdentifierGenerator().getClass().getName()
);
}
return performSave( entity, generatedId, persister, false, context, source, true );
}
}
@ -166,11 +165,28 @@ public abstract class AbstractSaveEventListener<C>
LOG.tracev( "Saving {0}", MessageHelper.infoString( persister, id, source.getFactory() ) );
}
final EntityKey key;
if ( !useIdentityColumn ) {
key = source.generateEntityKey( id, persister );
final EntityKey key = entityKey( entity, id, persister, useIdentityColumn, source );
if ( invokeSaveLifecycle( entity, persister, source ) ) {
return id;
}
else {
return performSaveOrReplicate(
entity,
key,
persister,
useIdentityColumn,
context,
source,
requiresImmediateIdAccess
);
}
}
private static EntityKey entityKey(Object entity, Object id, EntityPersister persister, boolean useIdentityColumn, EventSource source) {
if ( !useIdentityColumn) {
final EntityKey key = source.generateEntityKey( id, persister );
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
Object old = persistenceContext.getEntity( key );
final Object old = persistenceContext.getEntity( key );
if ( old != null ) {
if ( persistenceContext.getEntry( old ).getStatus() == Status.DELETED ) {
source.forceFlush( persistenceContext.getEntry( old ) );
@ -180,24 +196,11 @@ public abstract class AbstractSaveEventListener<C>
}
}
persister.setIdentifier( entity, id, source );
return key;
}
else {
key = null;
return null;
}
if ( invokeSaveLifecycle( entity, persister, source ) ) {
return id; //EARLY EXIT
}
return performSaveOrReplicate(
entity,
key,
persister,
useIdentityColumn,
context,
source,
requiresImmediateIdAccess
);
}
protected boolean invokeSaveLifecycle(Object entity, EntityPersister persister, EventSource source) {
@ -238,10 +241,9 @@ public abstract class AbstractSaveEventListener<C>
EventSource source,
boolean requiresImmediateIdAccess) {
Object id = key == null ? null : key.getIdentifier();
final Object id = key == null ? null : key.getIdentifier();
boolean inTrx = source.isTransactionInProgress();
boolean shouldDelayIdentityInserts = !inTrx && !requiresImmediateIdAccess;
boolean shouldDelayIdentityInserts = !source.isTransactionInProgress() && !requiresImmediateIdAccess;
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
// Put a placeholder in entries, so we don't recurse back and try to save() the
@ -262,29 +264,8 @@ public abstract class AbstractSaveEventListener<C>
cascadeBeforeSave( source, persister, entity, context );
Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( context ), source );
Type[] types = persister.getPropertyTypes();
boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );
if ( persister.hasCollections() ) {
substitute = visitCollectionsBeforeSave( entity, id, values, types, source ) || substitute;
}
if ( substitute ) {
persister.setValues( entity, values );
}
TypeHelper.deepCopy(
values,
types,
persister.getPropertyUpdateability(),
values,
source
);
final AbstractEntityInsertAction insert = addInsertAction(
values,
cloneAndSubstituteValues( entity, persister, context, source, id ),
id,
entity,
persister,
@ -296,20 +277,10 @@ public abstract class AbstractSaveEventListener<C>
// postpone initializing id in case the insert has non-nullable transient dependencies
// that are not resolved until cascadeAfterSave() is executed
cascadeAfterSave( source, persister, entity, context );
if ( useIdentityColumn && insert.isEarlyInsert() ) {
if ( !(insert instanceof EntityIdentityInsertAction) ) {
throw new IllegalStateException(
"Insert should be using an identity column, but action is of unexpected type: " +
insert.getClass().getName()
);
}
id = ((EntityIdentityInsertAction) insert).getGeneratedId();
insert.handleNaturalIdPostSaveNotifications( id );
}
final Object finalId = handleGeneratedId( useIdentityColumn, id, insert );
EntityEntry newEntry = persistenceContext.getEntry( entity );
if ( newEntry != original ) {
EntityEntryExtraState extraState = newEntry.getExtraState( EntityEntryExtraState.class );
if ( extraState == null ) {
@ -317,7 +288,49 @@ public abstract class AbstractSaveEventListener<C>
}
}
return id;
return finalId;
}
private static Object handleGeneratedId(boolean useIdentityColumn, Object id, AbstractEntityInsertAction insert) {
if ( useIdentityColumn && insert.isEarlyInsert() ) {
if ( insert instanceof EntityIdentityInsertAction ) {
Object generatedId = ((EntityIdentityInsertAction) insert).getGeneratedId();
insert.handleNaturalIdPostSaveNotifications( generatedId );
return generatedId;
}
else {
throw new IllegalStateException(
"Insert should be using an identity column, but action is of unexpected type: "
+ insert.getClass().getName()
);
}
}
else {
return id;
}
}
private Object[] cloneAndSubstituteValues(Object entity, EntityPersister persister, C context, EventSource source, Object id) {
Object[] values = persister.getPropertyValuesToInsert(entity, getMergeMap(context), source);
Type[] types = persister.getPropertyTypes();
boolean substitute = substituteValuesIfNecessary(entity, id, values, persister, source);
if ( persister.hasCollections() ) {
substitute = visitCollectionsBeforeSave(entity, id, values, types, source) || substitute;
}
if ( substitute ) {
persister.setValues(entity, values );
}
TypeHelper.deepCopy(
values,
types,
persister.getPropertyUpdateability(),
values,
source
);
return values;
}
private AbstractEntityInsertAction addInsertAction(
@ -434,7 +447,6 @@ public abstract class AbstractSaveEventListener<C>
EntityPersister persister,
Object entity,
C context) {
// cascade-save to many-to-one BEFORE the parent is saved
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
persistenceContext.incrementCascadeLevel();
@ -466,9 +478,8 @@ public abstract class AbstractSaveEventListener<C>
EntityPersister persister,
Object entity,
C context) {
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
// cascade-save to collections AFTER the collection owner was saved
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
persistenceContext.incrementCascadeLevel();
try {
Cascade.cascade(

View File

@ -27,7 +27,6 @@ import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.service.spi.JpaBootstrapSensitive;
import org.hibernate.event.spi.DeleteContext;
@ -119,7 +118,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
if ( persister.hasOwnedCollections() ) {
// we're deleting an unloaded proxy with collections
for ( Type type : persister.getPropertyTypes() ) { //TODO: when we enable this for subclasses use getSubclassPropertyTypeClosure()
deleteOwnedCollections( type, id, source, source.getActionQueue() );
deleteOwnedCollections( type, id, source );
}
}
@ -132,8 +131,9 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
return false;
}
private void deleteOwnedCollections(Type type, Object key, SharedSessionContractImplementor session, ActionQueue actionQueue) {
private static void deleteOwnedCollections(Type type, Object key, EventSource session) {
MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel();
ActionQueue actionQueue = session.getActionQueue();
if ( type.isCollectionType() ) {
String role = ( (CollectionType) type ).getRole();
CollectionPersister persister = mappingMetamodel.getCollectionDescriptor(role);
@ -144,7 +144,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
else if ( type.isComponentType() ) {
Type[] subtypes = ( (CompositeType) type ).getSubtypes();
for ( Type subtype : subtypes ) {
deleteOwnedCollections( subtype, key, session, actionQueue );
deleteOwnedCollections( subtype, key, session );
}
}
}
@ -161,16 +161,12 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
}
}
private void deleteTransientInstance(
DeleteEvent event,
DeleteContext transientEntities,
Object entity) {
private void deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) {
LOG.trace( "Entity was not persistent in delete processing" );
final EventSource source = event.getSession();
EntityPersister persister = source.getEntityPersister(event.getEntityName(), entity);
EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities );
}
@ -194,7 +190,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
EntityEntry entityEntry = persistenceContext.addEntity(
entity,
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
persister.getValues(entity),
persister.getValues( entity ),
key,
version,
LockMode.NONE,
@ -213,11 +209,8 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
DeleteContext transientEntities,
Object entity,
EntityEntry entityEntry) {
LOG.trace( "Deleting a persistent instance" );
final EventSource source = event.getSession();
if ( entityEntry.getStatus() == Status.DELETED || entityEntry.getStatus() == Status.GONE
|| source.getPersistenceContextInternal()
.containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) {
@ -245,24 +238,20 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
Object id,
Object version,
EntityEntry entityEntry) {
callbackRegistry.preRemove(entity);
if ( invokeDeleteLifecycle(source, entity, persister) ) {
return;
}
deleteEntity(
source,
entity,
entityEntry,
event.isCascadeDeleteEnabled(),
event.isOrphanRemovalBeforeUpdates(),
persister,
transientEntities
);
if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) {
persister.resetIdentifier(entity, id, version, source);
if ( !invokeDeleteLifecycle( source, entity, persister ) ) {
deleteEntity(
source,
entity,
entityEntry,
event.isCascadeDeleteEnabled(),
event.isOrphanRemovalBeforeUpdates(),
persister,
transientEntities
);
if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) {
persister.resetIdentifier( entity, id, version, source );
}
}
}
@ -467,9 +456,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
for ( int i = 0; i < types.length; i++) {
if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) {
final CollectionType collectionType = (CollectionType) types[i];
final CollectionPersister collectionDescriptor = persister.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
final CollectionPersister collectionDescriptor = persister.getFactory().getMappingMetamodel()
.getCollectionDescriptor( collectionType.getRole() );
if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) {
final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() );

View File

@ -41,83 +41,77 @@ public class DefaultEvictEventListener implements EvictEventListener {
*
*/
public void onEvict(EvictEvent event) throws HibernateException {
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final Object object = event.getObject();
if ( object == null ) {
throw new NullPointerException( "null passed to Session.evict()" );
}
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
if ( object instanceof HibernateProxy ) {
final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
final Object id = li.getInternalIdentifier();
if ( id == null ) {
throw new IllegalArgumentException( "Could not determine identifier of proxy passed to evict()" );
}
final EntityPersister persister = source.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( li.getEntityName() );
final EntityKey key = source.generateEntityKey( id, persister );
persistenceContext.removeProxy( key );
if ( !li.isUninitialized() ) {
final Object entity = persistenceContext.removeEntity( key );
if ( entity != null ) {
EntityEntry e = persistenceContext.removeEntry( entity );
doEvict( entity, key, e.getPersister(), event.getSession() );
EntityEntry entry = persistenceContext.removeEntry( entity );
doEvict( entity, key, entry.getPersister(), event.getSession() );
}
}
li.unsetSession();
}
else {
EntityEntry e = persistenceContext.getEntry( object );
if ( e != null ) {
doEvict( object, e.getEntityKey(), e.getPersister(), source );
EntityEntry entry = persistenceContext.getEntry( object );
if ( entry != null ) {
doEvict( object, entry.getEntityKey(), entry.getPersister(), source );
}
else {
// see if the passed object is even an entity, and if not throw an exception
// this is different than legacy Hibernate behavior, but what JPA 2.1 is calling for
// with EntityManager.detach
EntityPersister persister = null;
final String entityName = persistenceContext.getSession().guessEntityName( object );
if ( entityName != null ) {
try {
persister = source.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( entityName );
}
catch (Exception ignore) {
}
}
if ( persister == null ) {
throw new IllegalArgumentException( "Non-entity object instance passed to evict : " + object );
}
checkEntity( object, source );
}
}
}
/**
* Make sure the passed object is even an entity, and if not throw an exception.
* This is different to the legacy Hibernate behavior, but is what JPA 2.1
* requires with EntityManager.detach().
*/
private static void checkEntity(Object object, EventSource source) {
String entityName = source.getSession().guessEntityName( object );
if ( entityName != null ) {
try {
EntityPersister persister = source.getFactory().getMappingMetamodel()
.getEntityDescriptor( entityName );
if ( persister != null ) {
return; //ALL GOOD
}
}
catch (Exception ignore) {
}
}
throw new IllegalArgumentException( "Non-entity object instance passed to evict: " + object);
}
protected void doEvict(
final Object object,
final EntityKey key,
final EntityPersister persister,
final EventSource session)
throws HibernateException {
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Evicting {0}", MessageHelper.infoString( persister ) );
}
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( persister.hasNaturalIdentifier() ) {
persistenceContext.getNaturalIdResolutions().handleEviction(
object,
key.getIdentifier(),
persister
);
persistenceContext.getNaturalIdResolutions().handleEviction( object, key.getIdentifier(), persister );
}
// remove all collections for the entity from the session-level cache

View File

@ -78,7 +78,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
if ( !persister.getIdentifierType().isEqual( id, oid, session.getFactory() ) ) {
throw new HibernateException( "identifier of an instance of " + persister.getEntityName()
+ " was altered from " + oid + " to " + id );
+ " was altered from " + oid + " to " + id );
}
}
}
@ -348,11 +348,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
private boolean copyState(Object entity, Type[] types, Object[] state, SessionFactoryImplementor factory) {
// copy the entity state into the state array and return true if the state has changed
final Object[] newState = factory.getRuntimeMetamodels()
.getEntityMappingType( entity.getClass() )
.getEntityPersister()
.getValues( entity );
final Object[] newState = currentState( entity, factory );
boolean isDirty = false;
for ( int index = 0, size = newState.length; index < size; index++ ) {
if ( isDirty( types[index], state[index], newState[index] ) ) {
@ -363,6 +359,13 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
return isDirty;
}
private static Object[] currentState(Object entity, SessionFactoryImplementor factory) {
return factory.getRuntimeMetamodels()
.getEntityMappingType( entity.getClass() )
.getEntityPersister()
.getValues( entity );
}
private static boolean isDirty(Type types, Object state, Object newState) {
return state == UNFETCHED_PROPERTY && newState != UNFETCHED_PROPERTY
|| state != newState && !types.isEqual( state, newState );

View File

@ -29,8 +29,8 @@ public class DefaultFlushEventListener extends AbstractFlushingEventListener imp
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
if ( persistenceContext.getNumberOfManagedEntities() > 0 ||
persistenceContext.getCollectionEntriesSize() > 0 ) {
if ( persistenceContext.getNumberOfManagedEntities() > 0
|| persistenceContext.getCollectionEntriesSize() > 0 ) {
try {
source.getEventListenerManager().flushStart();

View File

@ -16,10 +16,9 @@ import org.hibernate.TypeMismatchException;
import org.hibernate.action.internal.DelayedPostInsertIdentifier;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
@ -28,6 +27,7 @@ import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
import org.hibernate.loader.entity.CacheEntityLoaderHelper.PersistenceContextEntry;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
@ -39,7 +39,6 @@ import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tuple.entity.EntityMetamodel;
/**
* Defines the default load event listeners used by hibernate for loading entities
@ -56,48 +55,40 @@ public class DefaultLoadEventListener implements LoadEventListener {
*
* @param event The load event to be handled.
*/
public void onLoad(
final LoadEvent event,
final LoadType loadType) throws HibernateException {
public void onLoad(LoadEvent event, LoadType loadType) throws HibernateException {
final EntityPersister persister = getPersister( event );
if ( persister == null ) {
throw new HibernateException( "Unable to locate persister: " + event.getEntityClassName() );
}
if ( !persister.getIdentifierMapping().getJavaType().isInstance( event.getEntityId() ) &&
!( event.getEntityId() instanceof DelayedPostInsertIdentifier ) ) {
checkIdClass( persister, event, loadType, persister.getIdentifierType().getReturnedClass() );
final Object id = event.getEntityId();
if ( !persister.getIdentifierMapping().getJavaType().isInstance( id )
&& !( id instanceof DelayedPostInsertIdentifier ) ) {
final Class<?> idClass = persister.getIdentifierType().getReturnedClass();
if ( handleIdType( persister, event, loadType, idClass) ) {
throw new TypeMismatchException(
"Provided id of the wrong type for class " + persister.getEntityName()
+ ". Expected: " + idClass
+ ", got " + event.getEntityId().getClass()
);
}
}
doOnLoad( persister, event, loadType );
}
protected EntityPersister getPersister(final LoadEvent event) {
final Object instanceToLoad = event.getInstanceToLoad();
final EventSource source = event.getSession();
if ( instanceToLoad != null ) {
//the load() which takes an entity does not pass an entityName
event.setEntityClassName( instanceToLoad.getClass().getName() );
return event.getSession().getEntityPersister(
null,
instanceToLoad
);
return source.getEntityPersister( null, instanceToLoad );
}
else {
return event.getSession()
.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( event.getEntityClassName() );
return source.getFactory().getMappingMetamodel().getEntityDescriptor( event.getEntityClassName() );
}
}
private void doOnLoad(
final EntityPersister persister,
final LoadEvent event,
final LoadType loadType) {
private void doOnLoad(EntityPersister persister, LoadEvent event, LoadType loadType) {
try {
final EventSource session = event.getSession();
final EntityKey keyToLoad = session.generateEntityKey( event.getEntityId(), persister );
@ -108,12 +99,10 @@ public class DefaultLoadEventListener implements LoadEventListener {
}
else {
//return a proxy if appropriate
if ( event.getLockMode() == LockMode.NONE ) {
event.setResult( proxyOrLoad( event, persister, keyToLoad, loadType ) );
}
else {
event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, session ) );
}
Object result = event.getLockMode() == LockMode.NONE
? proxyOrLoad( event, persister, keyToLoad, loadType )
: lockAndLoad( event, persister, keyToLoad, loadType );
event.setResult( result );
}
}
catch (HibernateException e) {
@ -122,13 +111,10 @@ public class DefaultLoadEventListener implements LoadEventListener {
}
}
private void checkIdClass(
final EntityPersister persister,
final LoadEvent event,
final LoadType loadType,
final Class<?> idClass) {
//TODO: this method is completely unreadable, clean it up:
private boolean handleIdType(EntityPersister persister, LoadEvent event, LoadType loadType, Class<?> idClass) {
// we may have the jpa requirement of allowing find-by-id where id is the "simple pk value" of a
// dependent objects parent. This is part of its generally goofy derived identity "feature"
// dependent objects parent. This is part of its generally goofy derived identity "feature"
final EntityIdentifierMapping idMapping = persister.getIdentifierMapping();
if ( idMapping instanceof CompositeIdentifierMapping ) {
final CompositeIdentifierMapping compositeIdMapping = (CompositeIdentifierMapping) idMapping;
@ -150,25 +136,26 @@ public class DefaultLoadEventListener implements LoadEventListener {
compositeIdMapping,
(EntityPersister) parentIdTargetMapping
);
return;
return false;
}
else {
return true;
}
}
else if ( idClass.isInstance( event.getEntityId() ) ) {
return;
else {
return !idClass.isInstance( event.getEntityId() );
}
}
else if ( idMapping instanceof NonAggregatedIdentifierMapping ) {
if ( idClass.isInstance( event.getEntityId() ) ) {
return;
}
return !idClass.isInstance( event.getEntityId() );
}
else {
return true;
}
}
throw new TypeMismatchException(
"Provided id of the wrong type for class " + persister.getEntityName()
+ ". Expected: " + idClass
+ ", got " + event.getEntityId().getClass()
);
else {
return true;
}
}
private void loadByDerivedIdentitySimplePkValue(
@ -180,12 +167,10 @@ public class DefaultLoadEventListener implements LoadEventListener {
final EventSource session = event.getSession();
final EntityKey parentEntityKey = session.generateEntityKey( event.getEntityId(), parentPersister );
final Object parent = doLoad( event, parentPersister, parentEntityKey, options );
final Object dependent = dependentIdType.instantiate();
dependentIdType.getPartMappingType().setValues( dependent, new Object[] { parent } );
final EntityKey dependentEntityKey = session.generateEntityKey( dependent, dependentPersister );
event.setEntityId( dependent );
event.setResult( doLoad( event, dependentPersister, dependentEntityKey, options ) );
}
@ -199,40 +184,28 @@ public class DefaultLoadEventListener implements LoadEventListener {
*
* @return The loaded entity.
*/
private Object load(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options) {
private Object load(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
final EventSource session = event.getSession();
if ( event.getInstanceToLoad() != null ) {
if ( session.getPersistenceContextInternal().getEntry( event.getInstanceToLoad() ) != null ) {
throw new PersistentObjectException(
"attempted to load into an instance that was already associated with the session: " +
MessageHelper.infoString(
persister,
event.getEntityId(),
session.getFactory()
)
"attempted to load into an instance that was already associated with the session: "
+ MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
);
}
persister.setIdentifier( event.getInstanceToLoad(), event.getEntityId(), session);
}
final Object entity = doLoad( event, persister, keyToLoad, options );
boolean isOptionalInstance = event.getInstanceToLoad() != null;
if ( entity == null && ( !options.isAllowNulls() || isOptionalInstance ) ) {
session.getFactory()
.getEntityNotFoundDelegate()
if ( entity == null
&& ( !options.isAllowNulls() || isOptionalInstance ) ) {
session.getFactory().getEntityNotFoundDelegate()
.handleEntityNotFound( event.getEntityClassName(), event.getEntityId() );
}
else if ( isOptionalInstance && entity != event.getInstanceToLoad() ) {
throw new NonUniqueObjectException( event.getEntityId(), event.getEntityClassName() );
}
return entity;
}
@ -247,124 +220,146 @@ public class DefaultLoadEventListener implements LoadEventListener {
*
* @return The result of the proxy/load operation.
*/
private Object proxyOrLoad(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options) {
final EventSource session = event.getSession();
final SessionFactoryImplementor factory = session.getFactory();
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
private Object proxyOrLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Loading entity: {0}",
MessageHelper.infoString( persister, event.getEntityId(), factory )
MessageHelper.infoString( persister, event.getEntityId(), persister.getFactory() )
);
}
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
// Check for the case where we can use the entity itself as a proxy
if ( options.isAllowProxyCreation()
&& entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) {
// if there is already a managed entity instance associated with the PC, return it
final Object managed = persistenceContext.getEntity( keyToLoad );
if ( managed != null ) {
if ( options.isCheckDeleted() ) {
final Status status = persistenceContext.getEntry( managed ).getStatus();
if ( status == Status.DELETED || status == Status.GONE ) {
return null;
}
}
return managed;
}
// if the entity defines a HibernateProxy factory, see if there is an
// existing proxy associated with the PC - and if so, use it
if ( persister.getRepresentationStrategy().getProxyFactory() != null ) {
if ( persistenceContext.containsDeletedUnloadedEntityKey( keyToLoad ) ) {
return null;
}
final Object proxy = persistenceContext.getProxy( keyToLoad );
if ( proxy != null ) {
if( traceEnabled ) {
LOG.trace( "Entity proxy found in session cache" );
}
if ( LOG.isDebugEnabled() && ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) {
LOG.debug( "Ignoring NO_PROXY to honor laziness" );
}
return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null );
}
// specialized handling for entities with subclasses with a HibernateProxy factory
if ( entityMetamodel.hasSubclasses() ) {
final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
session,
null,
LockMode.NONE,
persister,
keyToLoad
);
if ( cachedEntity != null ) {
return cachedEntity;
}
// entities with subclasses that define a ProxyFactory can create a HibernateProxy
return createProxy( event, persister, keyToLoad, persistenceContext );
}
}
if ( !entityMetamodel.hasSubclasses() ) {
if ( keyToLoad.isBatchLoadable() ) {
// Add a batch-fetch entry into the queue for this entity
persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
}
// This is the crux of HHH-11147
// create the (uninitialized) entity instance - has only id set
return persister.getBytecodeEnhancementMetadata()
.createEnhancedProxy( keyToLoad, true, session );
}
// If we get here, then the entity class has subclasses and there is no HibernateProxy factory.
// The entity will get loaded below.
if ( hasBytecodeProxy( persister, options ) ) {
return loadWithBytecodeProxy( event, persister, keyToLoad, options );
}
else if ( persister.hasProxy() ) {
return loadWithRegularProxy( event, persister, keyToLoad, options );
}
else {
if ( persister.hasProxy() ) {
// look for a proxy
Object proxy = persistenceContext.getProxy( keyToLoad );
if ( proxy != null ) {
return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy );
}
if ( options.isAllowProxyCreation() ) {
if ( entityMetamodel.hasSubclasses() ) {
final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
session,
null,
LockMode.NONE,
persister,
keyToLoad
);
if ( cachedEntity != null ) {
return cachedEntity;
}
}
return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext );
}
}
// no proxies, just return a newly loaded object
return load( event, persister, keyToLoad, options );
}
// return a newly loaded object
return load( event, persister, keyToLoad, options );
}
private Object loadWithBytecodeProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
// This is the case where we can use the entity itself as a proxy:
// if there is already a managed entity instance associated with the PC, return it
final EventSource session = event.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final Object managed = persistenceContext.getEntity( keyToLoad );
if ( managed != null ) {
return options.isCheckDeleted() && wasDeleted( persistenceContext, managed ) ? null : managed;
}
else if ( persister.getRepresentationStrategy().getProxyFactory() != null ) {
// we have a HibernateProxy factory, this case is more complicated
return loadWithProxyFactory( event, persister, keyToLoad );
}
else if ( persister.hasSubclasses() ) {
// the entity class has subclasses and there is no HibernateProxy factory
return load( event, persister, keyToLoad, options );
}
else {
// no HibernateProxy factory, and no subclasses
return createBatchLoadableEnhancedProxy( persister, keyToLoad, session );
}
}
private Object loadWithRegularProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
// This is the case where the proxy is a separate object:
// look for a proxy
final Object proxy = event.getSession().getPersistenceContextInternal().getProxy( keyToLoad );
if ( proxy != null ) {
// narrow the existing proxy to the type we're looking for
return narrowedProxy( event, persister, keyToLoad, options, proxy );
}
else if ( options.isAllowProxyCreation() ) {
// return a new proxy
return proxyOrCached( event, persister, keyToLoad, options );
}
else {
// return a newly loaded object
return load( event, persister, keyToLoad, options );
}
}
private static boolean hasBytecodeProxy(EntityPersister persister, LoadType options) {
return options.isAllowProxyCreation()
&& persister.getEntityMetamodel().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
}
private static Object loadWithProxyFactory(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) {
final EventSource session = event.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
// if ( persistenceContext.containsDeletedUnloadedEntityKey( keyToLoad ) ) {
// // an unloaded proxy with this key was deleted
// return null;
// }
// else {
// if the entity defines a HibernateProxy factory, see if there is an
// existing proxy associated with the PC - and if so, use it
final Object proxy = persistenceContext.getProxy( keyToLoad );
if ( proxy != null ) {
if ( LOG.isTraceEnabled() ) {
LOG.trace( "Entity proxy found in session cache" );
}
if ( LOG.isDebugEnabled()
&& ( (HibernateProxy) proxy ).getHibernateLazyInitializer().isUnwrap() ) {
LOG.debug( "Ignoring NO_PROXY to honor laziness" );
}
return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null );
}
else if ( persister.hasSubclasses() ) {
// specialized handling for entities with subclasses with a HibernateProxy factory
return proxyOrCached( event, persister, keyToLoad );
}
else {
// no existing proxy, and no subclasses
return createBatchLoadableEnhancedProxy( persister, keyToLoad, session );
}
// }
}
private static PersistentAttributeInterceptable createBatchLoadableEnhancedProxy(
EntityPersister persister,
EntityKey keyToLoad,
EventSource session) {
if ( keyToLoad.isBatchLoadable() ) {
// Add a batch-fetch entry into the queue for this entity
session.getPersistenceContextInternal().getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
}
// This is the crux of HHH-11147
// create the (uninitialized) entity instance - has only id set
return persister.getBytecodeEnhancementMetadata().createEnhancedProxy( keyToLoad, true, session );
}
private static Object proxyOrCached(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) {
final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
event.getSession(),
null,
LockMode.NONE,
persister,
keyToLoad
);
if ( cachedEntity != null ) {
return cachedEntity;
}
// entities with subclasses that define a ProxyFactory can create a HibernateProxy
return createProxy( event, persister, keyToLoad );
}
private static Object proxyOrCached(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
if ( persister.hasSubclasses() ) {
final Object cachedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
event.getSession(),
null,
LockMode.NONE,
persister,
keyToLoad
);
if ( cachedEntity != null ) {
return cachedEntity;
}
}
return createProxyIfNecessary( event, persister, keyToLoad, options );
}
/**
* Given a proxy, initialize it and/or narrow it provided either
@ -374,46 +369,46 @@ public class DefaultLoadEventListener implements LoadEventListener {
* @param persister The persister corresponding to the entity to be loaded
* @param keyToLoad The key of the entity to be loaded
* @param options The defined load options
* @param persistenceContext The originating session
* @param proxy The proxy to narrow
*
* @return The created/existing proxy
*/
private Object returnNarrowedProxy(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options,
final PersistenceContext persistenceContext,
final Object proxy) {
private Object narrowedProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options, Object proxy) {
if ( LOG.isTraceEnabled() ) {
LOG.trace( "Entity proxy found in session cache" );
}
LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
final LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
if ( li.isUnwrap() ) {
return li.getImplementation();
}
Object impl = null;
if ( !options.isAllowProxyCreation() ) {
impl = load( event, persister, keyToLoad, options );
if ( impl == null ) {
if ( options == LoadEventListener.INTERNAL_LOAD_NULLABLE ) {
// The proxy is for a non-existing association mapped as @NotFound.
// Don't throw an exeption; just return null.
return null;
}
else {
event.getSession()
.getFactory()
.getEntityNotFoundDelegate()
.handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() );
}
else {
final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal();
if ( options.isAllowProxyCreation() ) {
return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null );
}
else {
final Object impl = proxyImplementation( event, persister, keyToLoad, options );
return impl == null ? null : persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl );
}
}
}
return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl );
private Object proxyImplementation(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
Object entity = load( event, persister, keyToLoad, options );
if ( entity != null ) {
return entity;
}
else {
if ( options != LoadEventListener.INTERNAL_LOAD_NULLABLE ) {
// throw an appropriate exception
event.getSession().getFactory().getEntityNotFoundDelegate()
.handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() );
}
// Otherwise, if it's INTERNAL_LOAD_NULLABLE, the proxy is
// for a non-existing association mapped as @NotFound.
// Don't throw an exception; just return null.
return null;
}
}
/**
@ -425,46 +420,36 @@ public class DefaultLoadEventListener implements LoadEventListener {
* @param persister The persister corresponding to the entity to be loaded
* @param keyToLoad The key of the entity to be loaded
* @param options The defined load options
* @param persistenceContext The originating session
*
* @return The created/existing proxy
*/
private Object createProxyIfNecessary(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options,
final PersistenceContext persistenceContext) {
Object existing = persistenceContext.getEntity( keyToLoad );
private static Object createProxyIfNecessary(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal();
final Object existing = persistenceContext.getEntity( keyToLoad );
if ( existing != null ) {
// return existing object or initialized proxy (unless deleted)
if ( LOG.isTraceEnabled() ) {
LOG.trace( "Entity found in session cache" );
}
if ( options.isCheckDeleted() ) {
EntityEntry entry = persistenceContext.getEntry( existing );
Status status = entry.getStatus();
if ( status == Status.DELETED || status == Status.GONE ) {
return null;
}
}
return existing;
return options.isCheckDeleted() && wasDeleted( persistenceContext, existing ) ? null : existing;
}
else {
if ( LOG.isTraceEnabled() ) {
LOG.trace( "Creating new proxy for entity" );
}
return createProxy( event, persister, keyToLoad, persistenceContext );
return createProxy( event, persister, keyToLoad );
}
}
private Object createProxy(
LoadEvent event,
EntityPersister persister,
EntityKey keyToLoad,
PersistenceContext persistenceContext) {
private static boolean wasDeleted(PersistenceContext persistenceContext, Object existing) {
final Status status = persistenceContext.getEntry( existing ).getStatus();
return status == Status.DELETED || status == Status.GONE;
}
private static Object createProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) {
// return new uninitialized proxy
Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
final Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal();
persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
persistenceContext.addProxy( keyToLoad, proxy );
return proxy;
@ -478,19 +463,15 @@ public class DefaultLoadEventListener implements LoadEventListener {
* @param persister The persister corresponding to the entity to be loaded
* @param keyToLoad The key of the entity to be loaded
* @param options The defined load options
* @param source The originating session
*
* @return The loaded entity
*/
private Object lockAndLoad(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options,
final SessionImplementor source) {
SoftLock lock = null;
final Object ck;
private Object lockAndLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
final SessionImplementor source = event.getSession();;
final EntityDataAccess cache = persister.getCacheAccessStrategy();
final SoftLock lock;
final Object ck;
final boolean canWriteToCache = persister.canWriteToCache();
if ( canWriteToCache ) {
ck = cache.generateCacheKey(
@ -502,10 +483,11 @@ public class DefaultLoadEventListener implements LoadEventListener {
lock = cache.lockItem( source, ck, null );
}
else {
lock = null;
ck = null;
}
Object entity;
final Object entity;
try {
entity = load( event, persister, keyToLoad, options );
}
@ -532,58 +514,66 @@ public class DefaultLoadEventListener implements LoadEventListener {
*
* @return The loaded entity, or null.
*/
private Object doLoad(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadType options) {
final EventSource session = event.getSession();
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) {
private Object doLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Attempting to resolve: {0}",
MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
);
}
CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry
= CacheEntityLoaderHelper.INSTANCE.loadFromSessionCache( event, keyToLoad, options );
Object entity = persistenceContextEntry.getEntity();
if ( entity != null ) {
return persistenceContextEntry.isManaged() ? entity : null;
if ( event.getSession().getPersistenceContextInternal()
.containsDeletedUnloadedEntityKey( keyToLoad ) ) {
return null;
}
else {
PersistenceContextEntry persistenceContextEntry
= CacheEntityLoaderHelper.INSTANCE.loadFromSessionCache( event, keyToLoad, options );
final Object entity = persistenceContextEntry.getEntity();
if ( entity != null ) {
return persistenceContextEntry.isManaged() ? entity : null;
}
else {
return load( event, persister, keyToLoad );
}
}
}
entity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( event, persister, keyToLoad );
private Object load(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) {
final EventSource session = event.getSession();
final Object entity = loadFromCacheOrDatasource( event, persister, keyToLoad );
if ( entity != null && persister.hasNaturalIdentifier() ) {
session.getPersistenceContextInternal().getNaturalIdResolutions()
.cacheResolutionFromLoad(
event.getEntityId(),
persister.getNaturalIdMapping().extractNaturalIdFromEntity( entity, session ),
persister
);
}
return entity;
}
private Object loadFromCacheOrDatasource(LoadEvent event, EntityPersister persister, EntityKey keyToLoad) {
final EventSource session = event.getSession();
final Object entity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( event, persister, keyToLoad );
if ( entity != null ) {
if ( traceEnabled ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Resolved object in second-level cache: {0}",
MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
);
}
return entity;
}
else {
if ( traceEnabled ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Object not resolved in any cache: {0}",
MessageHelper.infoString( persister, event.getEntityId(), session.getFactory() )
);
}
entity = loadFromDatasource( event, persister );
return loadFromDatasource( event, persister );
}
if ( entity != null && persister.hasNaturalIdentifier() ) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
event.getEntityId(),
persister.getNaturalIdMapping().extractNaturalIdFromEntity( entity, session ),
persister
);
}
return entity;
}
/**
@ -595,9 +585,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
*
* @return The object loaded from the datasource, or null if not found.
*/
protected Object loadFromDatasource(
final LoadEvent event,
final EntityPersister persister) {
protected Object loadFromDatasource(final LoadEvent event, final EntityPersister persister) {
Object entity = persister.load(
event.getEntityId(),
event.getInstanceToLoad(),

View File

@ -67,14 +67,11 @@ public class DefaultLockEventListener extends AbstractLockUpgradeEventListener i
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final Object id = persister.getIdentifier( entity, source );
if ( !ForeignKeys.isNotTransient( event.getEntityName(), entity, Boolean.FALSE, source ) ) {
throw new TransientObjectException(
"cannot lock an unsaved transient instance: " +
persister.getEntityName()
);
throw new TransientObjectException( "cannot lock an unsaved transient instance: "
+ persister.getEntityName() );
}
entry = reassociate(event, entity, id, persister);
cascadeOnLock(event, persister, entity);
entry = reassociate( event, entity, id, persister );
cascadeOnLock( event, persister, entity );
}
upgradeLock( entity, entry, event.getLockOptions(), event.getSession() );

View File

@ -95,24 +95,18 @@ public class DefaultMergeEventListener
*
*/
public void onMerge(MergeEvent event, MergeContext copiedAlready) throws HibernateException {
final EventSource source = event.getSession();
final Object original = event.getOriginal();
// NOTE : `original` is the value being merged
if ( original != null ) {
final Object entity;
final EventSource source = event.getSession();
if ( original instanceof HibernateProxy ) {
LazyInitializer li = ( (HibernateProxy) original ).getHibernateLazyInitializer();
if ( li.isUninitialized() ) {
LOG.trace( "Ignoring uninitialized proxy" );
event.setResult( source.load( li.getEntityName(), li.getInternalIdentifier() ) );
//EARLY EXIT!
return;
}
else {
entity = li.getImplementation();
doMerge( event, copiedAlready, li.getImplementation() );
}
}
else if ( original instanceof PersistentAttributeInterceptable ) {
@ -122,121 +116,100 @@ public class DefaultMergeEventListener
final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
LOG.trace( "Ignoring uninitialized enhanced-proxy" );
event.setResult( source.load( proxyInterceptor.getEntityName(), proxyInterceptor.getIdentifier() ) );
//EARLY EXIT!
return;
}
else {
entity = original;
doMerge( event, copiedAlready, original );
}
}
else {
entity = original;
doMerge( event, copiedAlready, original );
}
if ( copiedAlready.containsKey( entity ) && copiedAlready.isOperatedOn( entity ) ) {
LOG.trace( "Already in merge process" );
event.setResult( entity );
}
else {
if ( copiedAlready.containsKey( entity ) ) {
LOG.trace( "Already in copyCache; setting in merge process" );
copiedAlready.setOperatedOn( entity, true );
}
event.setEntity( entity );
EntityState entityState = null;
// Check the persistence context for an entry relating to this
// entity to be merged...
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry == null ) {
EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
Object id = persister.getIdentifier( entity, source );
if ( id != null ) {
final EntityKey key = source.generateEntityKey( id, persister );
final Object managedEntity = persistenceContext.getEntity( key );
entry = persistenceContext.getEntry( managedEntity );
if ( entry != null ) {
// we have specialized case of a detached entity from the
// perspective of the merge operation. Specifically, we
// have an incoming entity instance which has a corresponding
// entry in the current persistence context, but registered
// under a different entity instance
entityState = EntityState.DETACHED;
}
}
}
if ( entityState == null ) {
entityState = EntityState.getEntityState( entity, event.getEntityName(), entry, source, false );
}
switch ( entityState ) {
case DETACHED:
entityIsDetached( event, copiedAlready);
break;
case TRANSIENT:
entityIsTransient( event, copiedAlready);
break;
case PERSISTENT:
entityIsPersistent( event, copiedAlready);
break;
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
null,
EventUtil.getLoggableName( event.getEntityName(), entity )
);
}
}
}
}
private void doMerge(MergeEvent event, MergeContext copiedAlready, Object entity) {
if ( copiedAlready.containsKey( entity ) && copiedAlready.isOperatedOn( entity ) ) {
LOG.trace( "Already in merge process" );
event.setResult( entity );
}
else {
if ( copiedAlready.containsKey( entity ) ) {
LOG.trace( "Already in copyCache; setting in merge process" );
copiedAlready.setOperatedOn( entity, true );
}
event.setEntity( entity );
merge( event, copiedAlready, entity );
}
}
private void merge(MergeEvent event, MergeContext copiedAlready, Object entity) {
switch ( entityState( event, entity ) ) {
case DETACHED:
entityIsDetached(event, copiedAlready);
break;
case TRANSIENT:
entityIsTransient(event, copiedAlready);
break;
case PERSISTENT:
entityIsPersistent(event, copiedAlready);
break;
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
null,
EventUtil.getLoggableName( event.getEntityName(), entity)
);
}
}
private static EntityState entityState(MergeEvent event, Object entity) {
final EventSource source = event.getSession();
// Check the persistence context for an entry relating to this
// entity to be merged...
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry == null ) {
EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
Object id = persister.getIdentifier( entity, source );
if ( id != null ) {
final Object managedEntity = persistenceContext.getEntity( source.generateEntityKey( id, persister ) );
entry = persistenceContext.getEntry( managedEntity );
if ( entry != null ) {
// we have a special case of a detached entity from the
// perspective of the merge operation. Specifically, we have
// an incoming entity instance which has a corresponding
// entry in the current persistence context, but registered
// under a different entity instance
return EntityState.DETACHED;
}
}
}
return EntityState.getEntityState( entity, event.getEntityName(), entry, source, false );
}
protected void entityIsPersistent(MergeEvent event, MergeContext copyCache) {
LOG.trace( "Ignoring persistent instance" );
//TODO: check that entry.getIdentifier().equals(requestedId)
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
copyCache.put( entity, entity, true ); //before cascade!
cascadeOnMerge( source, persister, entity, copyCache );
copyValues( persister, entity, entity, source, copyCache );
event.setResult( entity );
}
protected void entityIsTransient(MergeEvent event, MergeContext copyCache) {
LOG.trace( "Merging transient instance" );
final Object entity = event.getEntity();
final EventSource session = event.getSession();
final String entityName = event.getEntityName();
final EntityPersister persister = session.getEntityPersister( entityName, entity );
final Object id = persister.hasIdentifierProperty()
? persister.getIdentifier( entity, session )
: null;
final Object copy;
final Object existingCopy = copyCache.get( entity );
if ( existingCopy != null ) {
persister.setIdentifier( copyCache.get( entity ), id, session );
copy = existingCopy;
}
else {
copy = session.instantiate( persister, id );
//before cascade!
copyCache.put( entity, copy, true );
}
final Object copy = copyEntity( copyCache, entity, session, persister, id );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
@ -251,9 +224,15 @@ public class DefaultMergeEventListener
super.cascadeAfterSave( session, persister, entity, copyCache );
copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.TO_PARENT );
// saveTransientEntity has been called using a copy that contains empty collections (copyValues uses `ForeignKeyDirection.FROM_PARENT`)
// then the PC may contain a wrong collection snapshot, the CollectionVisitor realigns the collection snapshot values with the final copy
new CollectionVisitor( copy, id, session ).processEntityPropertyValues( persister.getPropertyValuesToInsert( copy, getMergeMap( copyCache ), session ), persister.getPropertyTypes() );
// saveTransientEntity has been called using a copy that contains empty collections
// (copyValues uses `ForeignKeyDirection.FROM_PARENT`) then the PC may contain a wrong
// collection snapshot, the CollectionVisitor realigns the collection snapshot values
// with the final copy
new CollectionVisitor( copy, id, session )
.processEntityPropertyValues(
persister.getPropertyValuesToInsert( copy, getMergeMap( copyCache ), session ),
persister.getPropertyTypes()
);
event.setResult( copy );
@ -266,11 +245,24 @@ public class DefaultMergeEventListener
}
}
private class CollectionVisitor extends WrapVisitor {
private static Object copyEntity(MergeContext copyCache, Object entity, EventSource session, EntityPersister persister, Object id) {
final Object existingCopy = copyCache.get( entity );
if ( existingCopy != null ) {
persister.setIdentifier( existingCopy, id, session );
return existingCopy;
}
else {
final Object copy = session.instantiate( persister, id );
//before cascade!
copyCache.put( entity, copy, true );
return copy;
}
}
private static class CollectionVisitor extends WrapVisitor {
CollectionVisitor(Object entity, Object id, EventSource session) {
super( entity, id, session );
}
@Override
Object processCollection(Object collection, CollectionType collectionType) throws HibernateException {
if ( collection instanceof PersistentCollection ) {
@ -288,7 +280,6 @@ public class DefaultMergeEventListener
}
return null;
}
@Override
Object processEntity(Object value, EntityType entityType) throws HibernateException {
return null;
@ -313,37 +304,22 @@ public class DefaultMergeEventListener
}
protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
LOG.trace( "Merging detached instance" );
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
final String entityName = persister.getEntityName();
Object id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source );
}
else {
// check that entity id = requestedId
Object entityId = persister.getIdentifier( entity, source );
if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) {
throw new HibernateException( "merge requested with id not matching id of passed entity" );
}
}
// we must clone embedded composite identifiers or we will get back the same instance that we pass in
Object id = getDetachedEntityId( event, entity, persister );
// we must clone embedded composite identifiers, or we will get back the same instance that we pass in
final Object clonedIdentifier = persister.getIdentifierType().deepCopy( id, source.getFactory() );
// apply the special MERGE fetch profile and perform the resolution (Session#get)
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
CascadingFetchProfile.MERGE,
() -> source.get( entityName, clonedIdentifier )
);
if ( result == null ) {
//TODO: we should throw an exception if we really *know* for sure
// that this is a detached instance, rather than just assuming
@ -357,41 +333,61 @@ public class DefaultMergeEventListener
else {
// before cascade!
copyCache.put( entity, result, true );
final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source );
if ( target == entity ) {
throw new AssertionFailure( "entity was not detached" );
}
else if ( !source.getEntityName( target ).equals( entityName ) ) {
throw new WrongClassException(
"class of the given object did not match class of persistent copy",
event.getRequestedId(),
entityName
);
}
else if ( isVersionChanged( entity, source, persister, target ) ) {
final StatisticsImplementor statistics = source.getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.optimisticFailure( entityName );
}
throw new StaleObjectStateException( entityName, id );
}
final Object target = targetEntity( event, entity, persister, id, result );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
cascadeOnMerge( source, persister, entity, copyCache );
copyValues( persister, entity, target, source, copyCache );
//copyValues works by reflection, so explicitly mark the entity instance dirty
markInterceptorDirty( entity, target );
event.setResult( result );
}
}
private Object unproxyManagedForDetachedMerging(
private static Object targetEntity(MergeEvent event, Object entity, EntityPersister persister, Object id, Object result) {
final EventSource source = event.getSession();
final String entityName = persister.getEntityName();
final Object target = unproxyManagedForDetachedMerging( entity, result, persister, source );
if ( target == entity) {
throw new AssertionFailure( "entity was not detached" );
}
else if ( !source.getEntityName( target ).equals( entityName ) ) {
throw new WrongClassException(
"class of the given object did not match class of persistent copy",
event.getRequestedId(),
entityName
);
}
else if ( isVersionChanged( entity, source, persister, target ) ) {
final StatisticsImplementor statistics = source.getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.optimisticFailure( entityName );
}
throw new StaleObjectStateException( entityName, id );
}
else {
return target;
}
}
private static Object getDetachedEntityId(MergeEvent event, Object entity, EntityPersister persister) {
final EventSource source = event.getSession();
final Object id = event.getRequestedId();
if ( id == null ) {
return persister.getIdentifier( entity, source );
}
else {
// check that entity id = requestedId
Object entityId = persister.getIdentifier( entity, source );
if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) {
throw new HibernateException( "merge requested with id not matching id of passed entity" );
}
return id;
}
}
private static Object unproxyManagedForDetachedMerging(
Object incoming,
Object managed,
EntityPersister persister,
@ -429,43 +425,43 @@ public class DefaultMergeEventListener
return managed;
}
private void markInterceptorDirty(final Object entity, final Object target) {
private static void markInterceptorDirty(final Object entity, final Object target) {
// for enhanced entities, copy over the dirty attributes
if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) {
// clear, because setting the embedded attributes dirties them
( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes();
for ( String fieldName : ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ) {
( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( fieldName );
}
}
}
private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {
if ( !persister.isVersioned() ) {
private static boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) {
if ( persister.isVersioned() ) {
// for merging of versioned entities, we consider the version having
// been changed only when:
// 1) the two version values are different;
// *AND*
// 2) The target actually represents database state!
//
// This second condition is a special case which allows
// an entity to be merged during the same transaction
// (though during a separate operation) in which it was
// originally persisted/saved
boolean changed = !persister.getVersionType().isSame(
persister.getVersion( target ),
persister.getVersion( entity )
);
// TODO : perhaps we should additionally require that the incoming entity
// version be equivalent to the defined unsaved-value?
return changed && existsInDatabase( target, source, persister );
}
else {
return false;
}
// for merging of versioned entities, we consider the version having
// been changed only when:
// 1) the two version values are different;
// *AND*
// 2) The target actually represents database state!
//
// This second condition is a special case which allows
// an entity to be merged during the same transaction
// (though during a separate operation) in which it was
// originally persisted/saved
boolean changed = !persister.getVersionType().isSame(
persister.getVersion( target ),
persister.getVersion( entity )
);
// TODO : perhaps we should additionally require that the incoming entity
// version be equivalent to the defined unsaved-value?
return changed && existsInDatabase( target, source, persister );
}
private boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) {
private static boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) {
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry == null ) {
@ -554,8 +550,7 @@ public class DefaultMergeEventListener
final EventSource source,
final EntityPersister persister,
final Object entity,
final MergeContext copyCache
) {
final MergeContext copyCache) {
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
persistenceContext.incrementCascadeLevel();
try {
@ -584,14 +579,12 @@ public class DefaultMergeEventListener
*/
@Override
protected void cascadeAfterSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything)
throws HibernateException {
}
throws HibernateException {}
/**
* Cascade behavior is redefined by this subclass, disable superclass behavior
*/
@Override
protected void cascadeBeforeSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything)
throws HibernateException {
}
throws HibernateException {}
}

View File

@ -12,7 +12,6 @@ import org.hibernate.PersistentObjectException;
import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.PersistContext;
@ -27,6 +26,8 @@ import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import static org.hibernate.event.internal.EntityState.getEntityState;
/**
* Defines the default create event listener used by hibernate for creating
* transient entities in response to generated create events.
@ -60,37 +61,55 @@ public class DefaultPersistEventListener
*
*/
public void onPersist(PersistEvent event, PersistContext createCache) throws HibernateException {
final SessionImplementor source = event.getSession();
final Object object = event.getObject();
final Object entity;
if ( object instanceof HibernateProxy ) {
LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
if ( li.isUninitialized() ) {
if ( li.getSession() == source ) {
return; //NOTE EARLY EXIT!
}
else {
if ( li.getSession() != event.getSession() ) {
throw new PersistentObjectException( "uninitialized proxy passed to persist()" );
}
}
entity = li.getImplementation();
else {
persist( event, createCache, li.getImplementation() );
}
}
else {
entity = object;
}
final String entityName;
if ( event.getEntityName() != null ) {
entityName = event.getEntityName();
}
else {
entityName = source.bestGuessEntityName( entity );
event.setEntityName( entityName );
persist( event, createCache, object );
}
}
private void persist(PersistEvent event, PersistContext createCache, Object entity) {
final EventSource source = event.getSession();
final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity );
EntityState entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true );
final String entityName = entityName( event, entity );
switch ( entityState( event, entity, entityName, entityEntry ) ) {
case DETACHED:
throw new PersistentObjectException( "detached entity passed to persist: "
+ EventUtil.getLoggableName( event.getEntityName(), entity) );
case PERSISTENT:
entityIsPersistent( event, createCache );
break;
case TRANSIENT:
entityIsTransient( event, createCache );
break;
case DELETED:
entityEntry.setStatus( Status.MANAGED );
entityEntry.setDeletedState( null );
source.getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
entityIsDeleted( event, createCache );
break;
default:
throw new ObjectDeletedException(
"deleted entity passed to persist",
null,
EventUtil.getLoggableName( event.getEntityName(), entity )
);
}
}
private static EntityState entityState(PersistEvent event, Object entity, String entityName, EntityEntry entityEntry) {
final EventSource source = event.getSession();
EntityState entityState = getEntityState( entity, entityName, entityEntry, source, true );
if ( entityState == EntityState.DETACHED ) {
// JPA 2, in its version of a "foreign generated", allows the id attribute value
// to be manually set by the user, even though this manual value is irrelevant.
@ -101,64 +120,38 @@ public class DefaultPersistEventListener
// entity state again.
// NOTE: entityEntry must be null to get here, so we cannot use any of its values
final EntityPersister persister = source.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
final EntityPersister persister = source.getFactory().getMappingMetamodel()
.getEntityDescriptor( entityName );
if ( persister.getIdentifierGenerator() instanceof ForeignGenerator ) {
if ( LOG.isDebugEnabled() && persister.getIdentifier( entity, source ) != null ) {
LOG.debug( "Resetting entity id attribute to null for foreign generator" );
}
persister.setIdentifier( entity, null, source );
entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true );
entityState = getEntityState( entity, entityName, entityEntry, source, true );
}
}
return entityState;
}
switch ( entityState ) {
case DETACHED: {
throw new PersistentObjectException(
"detached entity passed to persist: " +
EventUtil.getLoggableName( event.getEntityName(), entity )
);
}
case PERSISTENT: {
entityIsPersistent( event, createCache );
break;
}
case TRANSIENT: {
entityIsTransient( event, createCache );
break;
}
case DELETED: {
entityEntry.setStatus( Status.MANAGED );
entityEntry.setDeletedState( null );
event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
entityIsDeleted( event, createCache );
break;
}
default: {
throw new ObjectDeletedException(
"deleted entity passed to persist",
null,
EventUtil.getLoggableName( event.getEntityName(), entity )
);
}
private static String entityName(PersistEvent event, Object entity) {
if ( event.getEntityName() != null ) {
return event.getEntityName();
}
else {
// changes event.entityName by side effect!
final String entityName = event.getSession().bestGuessEntityName( entity );
event.setEntityName( entityName );
return entityName;
}
}
protected void entityIsPersistent(PersistEvent event, PersistContext createCache) {
LOG.trace( "Ignoring persistent instance" );
final EventSource source = event.getSession();
//TODO: check that entry.getIdentifier().equals(requestedId)
final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() );
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
if ( createCache.add( entity ) ) {
justCascade( createCache, source, entity, persister );
justCascade( createCache, source, entity, source.getEntityPersister( event.getEntityName(), entity ) );
}
}
@ -176,10 +169,8 @@ public class DefaultPersistEventListener
*/
protected void entityIsTransient(PersistEvent event, PersistContext createCache) {
LOG.trace( "Saving transient instance" );
final EventSource source = event.getSession();
final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() );
if ( createCache.add( entity ) ) {
saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false );
}
@ -187,21 +178,14 @@ public class DefaultPersistEventListener
private void entityIsDeleted(PersistEvent event, PersistContext createCache) {
final EventSource source = event.getSession();
final Object entity = source.getPersistenceContextInternal().unproxy( event.getObject() );
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"un-scheduling entity deletion [%s]",
MessageHelper.infoString(
persister,
persister.getIdentifier( entity, source ),
source.getFactory()
)
MessageHelper.infoString( persister, persister.getIdentifier( entity, source ), source.getFactory() )
);
}
if ( createCache.add( entity ) ) {
justCascade( createCache, source, entity, persister );
}

View File

@ -56,42 +56,45 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
* @param event The refresh event to be handled.
*/
public void onRefresh(RefreshEvent event, RefreshContext refreshedAlready) {
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
if ( persistenceContext.reassociateIfUninitializedProxy( event.getObject() ) ) {
boolean isTransient = event.getEntityName() != null
? !source.contains(event.getEntityName(), event.getObject())
: !source.contains(event.getObject());
if ( isTransient ) {
source.setReadOnly( event.getObject(), source.isDefaultReadOnly() );
final Object object = event.getObject();
if ( persistenceContext.reassociateIfUninitializedProxy( object ) ) {
if ( isTransient( event, source, object ) ) {
source.setReadOnly( object, source.isDefaultReadOnly() );
}
return;
}
final Object object = persistenceContext.unproxyAndReassociate( event.getObject() );
if ( !refreshedAlready.add( object ) ) {
LOG.trace( "Already refreshed" );
return;
else {
final Object entity = persistenceContext.unproxyAndReassociate( object );
if ( refreshedAlready.add( entity) ) {
refresh( event, refreshedAlready, entity );
}
else {
LOG.trace( "Already refreshed" );
}
}
}
private static boolean isTransient(RefreshEvent event, EventSource source, Object object) {
final String entityName = event.getEntityName();
return entityName != null ? !source.contains( entityName, object) : !source.contains(object);
}
private static void refresh(RefreshEvent event, RefreshContext refreshedAlready, Object object) {
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final EntityEntry entry = persistenceContext.getEntry( object );
final EntityPersister persister;
final Object id;
if ( entry == null ) {
//refresh() does not pass an entityName
persister = source.getEntityPersister( event.getEntityName(), object );
id = persister.getIdentifier( object, event.getSession() );
persister = source.getEntityPersister( event.getEntityName(), object);
id = persister.getIdentifier(object, event.getSession() );
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Refreshing transient {0}", MessageHelper.infoString(
persister,
id,
source.getFactory()
)
"Refreshing transient {0}",
MessageHelper.infoString( persister, id, source.getFactory() )
);
}
final EntityKey key = source.generateEntityKey( id, persister );
@ -105,11 +108,8 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
else {
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Refreshing ", MessageHelper.infoString(
entry.getPersister(),
entry.getId(),
source.getFactory()
)
"Refreshing ",
MessageHelper.infoString( entry.getPersister(), entry.getId(), source.getFactory() )
);
}
if ( !entry.isExistsInDatabase() ) {
@ -118,7 +118,6 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
"this instance does not yet exist as a row in the database"
);
}
persister = entry.getPersister();
id = entry.getId();
}
@ -137,17 +136,28 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
final EntityKey key = source.generateEntityKey( id, persister );
persistenceContext.removeEntity( key );
if ( persister.hasCollections() ) {
new EvictVisitor( source, object ).process( object, persister );
new EvictVisitor(source, object).process(object, persister );
}
}
evictEntity(object, persister, id, source);
evictCachedCollections( persister, id, source);
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
CascadingFetchProfile.REFRESH,
() -> doRefresh(event, source, object, entry, persister, id, persistenceContext)
);
UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() );
}
private static void evictEntity(Object object, EntityPersister persister, Object id, EventSource source) {
if ( persister.canWriteToCache() ) {
Object previousVersion = null;
if ( persister.isVersionPropertyGenerated() ) {
// we need to grab the version value from the entity, otherwise
// we have issues with generated-version entities that may have
// multiple actions queued during the same flush
previousVersion = persister.getVersion( object );
previousVersion = persister.getVersion(object);
}
final EntityDataAccess cache = persister.getCacheAccessStrategy();
final Object ck = cache.generateCacheKey(
@ -156,40 +166,26 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
source.getFactory(),
source.getTenantIdentifier()
);
final SoftLock lock = cache.lockItem( source, ck, previousVersion );
final SoftLock lock = cache.lockItem(source, ck, previousVersion );
cache.remove( source, ck );
source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) );
}
evictCachedCollections( persister, id, source );
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
CascadingFetchProfile.REFRESH,
() -> doRefresh( event, source, object, entry, persister, id, persistenceContext )
);
UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() );
}
private Object doRefresh(
private static Object doRefresh(
RefreshEvent event,
EventSource source,
Object object,
EntityEntry e,
EntityEntry entry,
EntityPersister persister,
Object id,
PersistenceContext persistenceContext) {
// Handle the requested lock-mode (if one) in relation to the entry's (if one) current lock-mode
LockOptions lockOptionsToUse = event.getLockOptions();
final LockMode requestedLockMode = lockOptionsToUse.getLockMode();
LockMode postRefreshLockMode = null;
if ( e != null ) {
final LockMode currentLockMode = e.getLockMode();
if ( entry != null ) {
final LockMode currentLockMode = entry.getLockMode();
if ( currentLockMode.greaterThan( requestedLockMode ) ) {
// the requested lock-mode is less restrictive than the current one
// - pass along the current lock-mode (after accounting for WRITE)
@ -205,7 +201,6 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
// WRITE specially because the Loader/Locker mechanism does not allow for WRITE
// locks
lockOptionsToUse.setLockMode( LockMode.READ );
// and prepare to reset the entry lock-mode to the previous lock mode after
// the refresh completes
postRefreshLockMode = currentLockMode;
@ -217,7 +212,6 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
}
final Object result = persister.load( id, object, lockOptionsToUse, source );
if ( result != null ) {
// apply `postRefreshLockMode`, if needed
if ( postRefreshLockMode != null ) {
@ -233,17 +227,17 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
source.setReadOnly( result, true );
}
else {
source.setReadOnly( result, e == null ? source.isDefaultReadOnly() : e.isReadOnly() );
source.setReadOnly( result, entry == null ? source.isDefaultReadOnly() : entry.isReadOnly() );
}
}
return result;
}
private void evictCachedCollections(EntityPersister persister, Object id, EventSource source) {
private static void evictCachedCollections(EntityPersister persister, Object id, EventSource source) {
evictCachedCollections( persister.getPropertyTypes(), id, source );
}
private void evictCachedCollections(Type[] types, Object id, EventSource source)
private static void evictCachedCollections(Type[] types, Object id, EventSource source)
throws HibernateException {
final ActionQueue actionQueue = source.getActionQueue();
final SessionFactoryImplementor factory = source.getFactory();
@ -251,7 +245,7 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
for ( Type type : types ) {
if ( type.isCollectionType() ) {
final String role = ((CollectionType) type).getRole();
CollectionPersister collectionPersister = metamodel.getCollectionDescriptor(role);
CollectionPersister collectionPersister = metamodel.getCollectionDescriptor( role );
if ( collectionPersister.hasCache() ) {
final CollectionDataAccess cache = collectionPersister.getCacheAccessStrategy();
final Object ck = cache.generateCacheKey(

View File

@ -6,11 +6,7 @@
*/
package org.hibernate.event.internal;
import java.util.concurrent.TimeUnit;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.ResolveNaturalIdEvent;
import org.hibernate.event.spi.ResolveNaturalIdEventListener;
@ -20,6 +16,9 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.stat.spi.StatisticsImplementor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
/**
* Defines the default load event listeners used by hibernate for loading entities
* in response to generated load events.
@ -35,8 +34,7 @@ public class DefaultResolveNaturalIdEventListener
@Override
public void onResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateException {
final Object entityId = resolveNaturalId( event );
event.setEntityId( entityId );
event.setEntityId( resolveNaturalId( event ) );
}
/**
@ -93,10 +91,7 @@ public class DefaultResolveNaturalIdEventListener
protected Object resolveFromCache(final ResolveNaturalIdEvent event) {
return event.getSession().getPersistenceContextInternal()
.getNaturalIdResolutions()
.findCachedIdByNaturalId(
event.getOrderedNaturalIdValues(),
event.getEntityPersister()
);
.findCachedIdByNaturalId( event.getOrderedNaturalIdValues(), event.getEntityPersister() );
}
/**
@ -124,20 +119,16 @@ public class DefaultResolveNaturalIdEventListener
if ( statisticsEnabled ) {
final long endTime = System.nanoTime();
final long milliseconds = TimeUnit.MILLISECONDS.convert( endTime - startTime, TimeUnit.NANOSECONDS );
statistics.naturalIdQueryExecuted(
event.getEntityPersister().getRootEntityName(),
milliseconds
);
final long milliseconds = MILLISECONDS.convert( endTime - startTime, NANOSECONDS );
statistics.naturalIdQueryExecuted( event.getEntityPersister().getRootEntityName(), milliseconds );
}
//PK can be null if the entity doesn't exist
if (pk != null) {
if ( pk != null ) {
session.getPersistenceContextInternal()
.getNaturalIdResolutions()
.cacheResolutionFromLoad( pk, event.getOrderedNaturalIdValues(), event.getEntityPersister() );
}
return pk;
}
}

View File

@ -24,13 +24,13 @@ public class DefaultSaveEventListener extends DefaultSaveOrUpdateEventListener {
// mappings, for the purpose of backward-compatibility
EntityEntry entry = event.getSession().getPersistenceContextInternal().getEntry( event.getEntity() );
return entry != null && entry.getStatus() != Status.DELETED
? entityIsPersistent(event)
: entityIsTransient(event);
? entityIsPersistent( event )
: entityIsTransient( event );
}
protected Object saveWithGeneratedOrRequestedId(SaveOrUpdateEvent event) {
if ( event.getRequestedId() == null ) {
return super.saveWithGeneratedOrRequestedId(event);
return super.saveWithGeneratedOrRequestedId( event );
}
else {
return saveWithRequestedId(
@ -41,7 +41,6 @@ public class DefaultSaveEventListener extends DefaultSaveOrUpdateEventListener {
event.getSession()
);
}
}
protected boolean reassociateIfUninitializedProxy(Object object, SessionImplementor source) {
@ -52,6 +51,4 @@ public class DefaultSaveEventListener extends DefaultSaveOrUpdateEventListener {
return false;
}
}
}

View File

@ -11,6 +11,7 @@ import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.PersistentObjectException;
import org.hibernate.TransientObjectException;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.classic.Lifecycle;
import org.hibernate.engine.internal.Cascade;
import org.hibernate.engine.internal.CascadePoint;
@ -75,7 +76,6 @@ public class DefaultSaveOrUpdateEventListener
//return the id in the event object
event.setResultId( performSaveOrUpdate( event ) );
}
}
protected boolean reassociateIfUninitializedProxy(Object object, SessionImplementor source) {
@ -209,7 +209,6 @@ public class DefaultSaveOrUpdateEventListener
* @param event The update event to be handled.
*/
protected void entityIsDetached(SaveOrUpdateEvent event) {
LOG.trace( "Updating detached instance" );
final EventSource session = event.getSession();
@ -219,15 +218,9 @@ public class DefaultSaveOrUpdateEventListener
}
Object entity = event.getEntity();
EntityPersister persister = session.getEntityPersister( event.getEntityName(), entity );
event.setRequestedId(
getUpdateId( entity, persister, event.getRequestedId(), session )
);
event.setRequestedId( getUpdateId( entity, persister, event.getRequestedId(), session ) );
performUpdate( event, entity, persister );
}
/**
@ -242,96 +235,79 @@ public class DefaultSaveOrUpdateEventListener
*
* @throws TransientObjectException If the entity is considered transient.
*/
protected Object getUpdateId(
Object entity,
EntityPersister persister,
Object requestedId,
SessionImplementor session) {
protected Object getUpdateId(Object entity, EntityPersister persister, Object requestedId, SessionImplementor session) {
// use the id assigned to the instance
Object id = persister.getIdentifier( entity, session );
if ( id == null ) {
// assume this is a newly instantiated transient object
// which should be saved rather than updated
throw new TransientObjectException(
"The given object has a null identifier: " +
persister.getEntityName()
);
throw new TransientObjectException( "The given object has a null identifier: "
+ persister.getEntityName() );
}
else {
return id;
}
}
protected void performUpdate(
SaveOrUpdateEvent event,
Object entity,
EntityPersister persister) throws HibernateException {
if ( LOG.isTraceEnabled() && !persister.isMutable() ) {
LOG.trace( "Immutable instance passed to performUpdate()" );
}
protected void performUpdate(SaveOrUpdateEvent event, Object entity, EntityPersister persister)
throws HibernateException {
final EventSource source = event.getSession();
if ( LOG.isTraceEnabled() ) {
if ( !persister.isMutable() ) {
LOG.trace( "Immutable instance passed to performUpdate()" );
}
LOG.tracev(
"Updating {0}",
MessageHelper.infoString( persister, event.getRequestedId(), event.getSession().getFactory() )
MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() )
);
}
final EventSource source = event.getSession();
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final EntityKey key = source.generateEntityKey( event.getRequestedId(), persister );
persistenceContext.checkUniqueness( key, entity );
if ( invokeUpdateLifecycle( entity, persister, source ) ) {
reassociate( event, event.getObject(), event.getRequestedId(), persister );
return;
}
else {
// this is a transient object with existing persistent state not loaded by the session
new OnUpdateVisitor( source, event.getRequestedId(), entity ).process( entity, persister );
// this is a transient object with existing persistent state not loaded by the session
// TODO: put this stuff back in to read snapshot from
// the second-level cache (needs some extra work)
// Object[] cachedState = null;
// if ( persister.hasCache() ) {
// CacheEntry entry = (CacheEntry) persister.getCache()
// .get( event.getRequestedId(), source.getTimestamp() );
// cachedState = entry==null ?
// null :
// entry.getState(); //TODO: half-assemble this stuff
// }
new OnUpdateVisitor( source, event.getRequestedId(), entity ).process( entity, persister );
// TODO: put this stuff back in to read snapshot from
// the second-level cache (needs some extra work)
/*Object[] cachedState = null;
if ( persister.hasCache() ) {
CacheEntry entry = (CacheEntry) persister.getCache()
.get( event.getRequestedId(), source.getTimestamp() );
cachedState = entry==null ?
null :
entry.getState(); //TODO: half-assemble this stuff
}*/
persistenceContext.addEntity(
entity,
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
null, // cachedState,
key,
persister.getVersion( entity ),
LockMode.NONE,
true,
persister,
false
);
persister.afterReassociate( entity, source );
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Updating {0}", MessageHelper.infoString(
persistenceContext.addEntity(
entity,
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
null, // cachedState,
key,
persister.getVersion( entity ),
LockMode.NONE,
true,
persister,
event.getRequestedId(),
source.getFactory()
)
false
);
}
cascadeOnUpdate( event, persister, entity );
persister.afterReassociate( entity, source );
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Updating {0}",
MessageHelper.infoString( persister, event.getRequestedId(), source.getFactory() )
);
}
cascadeOnUpdate( event, persister, entity );
}
}
protected boolean invokeUpdateLifecycle(Object entity, EntityPersister persister, EventSource source) {

View File

@ -25,15 +25,15 @@ public class DefaultUpdateEventListener extends DefaultSaveOrUpdateEventListener
// mappings, for the purpose of backward-compatibility
EntityEntry entry = event.getSession().getPersistenceContextInternal().getEntry( event.getEntity() );
if ( entry!=null ) {
if ( entry.getStatus()== Status.DELETED ) {
if ( entry.getStatus() == Status.DELETED ) {
throw new ObjectDeletedException( "deleted instance passed to update()", null, event.getEntityName() );
}
else {
return entityIsPersistent(event);
return entityIsPersistent( event );
}
}
else {
entityIsDetached(event);
entityIsDetached( event );
return null;
}
}
@ -42,13 +42,10 @@ public class DefaultUpdateEventListener extends DefaultSaveOrUpdateEventListener
* If the user specified an id, assign it to the instance and use that,
* otherwise use the id already assigned to the instance
*/
protected Object getUpdateId(
Object entity,
EntityPersister persister,
Object requestedId,
SessionImplementor session) throws HibernateException {
protected Object getUpdateId(Object entity, EntityPersister persister, Object requestedId, SessionImplementor session)
throws HibernateException {
if ( requestedId == null ) {
return super.getUpdateId( entity, persister, requestedId, session );
return super.getUpdateId( entity, persister, null, session );
}
else {
persister.setIdentifier( entity, requestedId, session );

View File

@ -17,7 +17,6 @@ import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SharedSessionContract;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.collection.internal.StandardArraySemantics;
import org.hibernate.collection.internal.StandardBagSemantics;
@ -73,8 +72,6 @@ import org.hibernate.metamodel.mapping.PropertyBasedMapping;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectableMappings;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.collection.CollectionPersister;

View File

@ -111,11 +111,15 @@ public class MultiLoadTest {
scope.inTransaction(
session -> {
// delete one of them (but do not flush)...
session.delete( session.load( SimpleEntity.class, 5 ) );
SimpleEntity s4 = session.load(SimpleEntity.class, 5);
session.delete( s4 );
assertFalse( Hibernate.isInitialized( s4 ) );
// as a baseline, assert based on how load() handles it
SimpleEntity s5 = session.load( SimpleEntity.class, 5 );
assertNotNull( s5 );
assertFalse( Hibernate.isInitialized( s5 ) );
}
);
}
@ -181,7 +185,7 @@ public class MultiLoadTest {
// and then, assert how get() handles it
SimpleEntity s5 = session.get( SimpleEntity.class, 5 );
assertNotNull( s5 ); //because we didn't flush the deletion!
assertNull( s5 );
}
);
}