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:
parent
fcd7a45a75
commit
8de46167f6
|
@ -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(
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue