HHH-7206 - Manage natural-id synchronization without flushing
This commit is contained in:
parent
52d5d374e5
commit
3800a0e695
|
@ -51,6 +51,24 @@ public interface NaturalIdLoadAccess {
|
||||||
*/
|
*/
|
||||||
public NaturalIdLoadAccess using(String attributeName, Object value);
|
public NaturalIdLoadAccess using(String attributeName, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For entities with mutable natural ids, should Hibernate perform "synchronization" prior to performing
|
||||||
|
* lookups? The default is to perform "synchronization" (for correctness).
|
||||||
|
* <p/>
|
||||||
|
* "synchronization" here indicates updating the natural-id -> pk cross reference maintained as part of the
|
||||||
|
* session. When enabled, prior to performing the lookup, Hibernate will check all entities of the given
|
||||||
|
* type associated with the session to see if its natural-id values have changed and, if so, update the
|
||||||
|
* cross reference. There is a performance impact associated with this, so if application developers are
|
||||||
|
* certain the natural-ids in play have not changed, this setting can be disabled to circumvent that impact.
|
||||||
|
* However, disabling this setting when natural-ids values have changed can result in incorrect results!
|
||||||
|
*
|
||||||
|
* @param enabled Should synchronization be performed? {@code true} indicates synchronization will be performed;
|
||||||
|
* {@code false} indicates it will be circumvented.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
public NaturalIdLoadAccess setSynchronizationEnabled(boolean enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}. This
|
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}. This
|
||||||
* method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
|
* method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
|
||||||
|
|
|
@ -42,6 +42,19 @@ public interface SimpleNaturalIdLoadAccess {
|
||||||
*/
|
*/
|
||||||
public SimpleNaturalIdLoadAccess with(LockOptions lockOptions);
|
public SimpleNaturalIdLoadAccess with(LockOptions lockOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For entities with mutable natural ids, should Hibernate perform "synchronization" prior to performing
|
||||||
|
* lookups? The default is to perform "synchronization" (for correctness).
|
||||||
|
* <p/>
|
||||||
|
* See {@link NaturalIdLoadAccess#setSynchronizationEnabled} for detailed discussion.
|
||||||
|
*
|
||||||
|
* @param enabled Should synchronization be performed? {@code true} indicates synchronization will be performed;
|
||||||
|
* {@code false} indicates it will be circumvented.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
public SimpleNaturalIdLoadAccess setSynchronizationEnabled(boolean enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the persistent instance with the given natural id value, assuming that the instance exists. This method
|
* Return the persistent instance with the given natural id value, assuming that the instance exists. This method
|
||||||
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
|
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.hibernate.engine.internal.ForeignKeys;
|
||||||
import org.hibernate.engine.internal.NonNullableTransientDependencies;
|
import org.hibernate.engine.internal.NonNullableTransientDependencies;
|
||||||
import org.hibernate.engine.internal.Nullability;
|
import org.hibernate.engine.internal.Nullability;
|
||||||
import org.hibernate.engine.internal.Versioning;
|
import org.hibernate.engine.internal.Versioning;
|
||||||
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
@ -70,6 +71,8 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
this.isVersionIncrementDisabled = isVersionIncrementDisabled;
|
this.isVersionIncrementDisabled = isVersionIncrementDisabled;
|
||||||
this.isExecuted = false;
|
this.isExecuted = false;
|
||||||
this.areTransientReferencesNullified = false;
|
this.areTransientReferencesNullified = false;
|
||||||
|
|
||||||
|
handleNaturalIdPreSaveNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,4 +175,32 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
this.state = entityEntry.getLoadedState();
|
this.state = entityEntry.getLoadedState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sending notifications needed for natural-id before saving
|
||||||
|
*/
|
||||||
|
protected void handleNaturalIdPreSaveNotifications() {
|
||||||
|
// before save, we need to add a local (transactional) natural id cross-reference
|
||||||
|
getSession().getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference(
|
||||||
|
getPersister(),
|
||||||
|
getId(),
|
||||||
|
state,
|
||||||
|
null,
|
||||||
|
CachedNaturalIdValueSource.INSERT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sending notifications needed for natural-id after saving
|
||||||
|
*/
|
||||||
|
protected void handleNaturalIdPostSaveNotifications() {
|
||||||
|
// after save, we need to manage the shared cache entries
|
||||||
|
getSession().getPersistenceContext().getNaturalIdHelper().manageSharedNaturalIdCrossReference(
|
||||||
|
getPersister(),
|
||||||
|
getId(),
|
||||||
|
state,
|
||||||
|
null,
|
||||||
|
CachedNaturalIdValueSource.INSERT
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.hibernate.AssertionFailure;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.cache.spi.CacheKey;
|
import org.hibernate.cache.spi.CacheKey;
|
||||||
import org.hibernate.cache.spi.access.SoftLock;
|
import org.hibernate.cache.spi.access.SoftLock;
|
||||||
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
@ -46,6 +47,7 @@ public final class EntityDeleteAction extends EntityAction {
|
||||||
private final Object[] state;
|
private final Object[] state;
|
||||||
|
|
||||||
private SoftLock lock;
|
private SoftLock lock;
|
||||||
|
private Object[] naturalIdValues;
|
||||||
|
|
||||||
public EntityDeleteAction(
|
public EntityDeleteAction(
|
||||||
final Serializable id,
|
final Serializable id,
|
||||||
|
@ -59,6 +61,13 @@ public final class EntityDeleteAction extends EntityAction {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.isCascadeDeleteEnabled = isCascadeDeleteEnabled;
|
this.isCascadeDeleteEnabled = isCascadeDeleteEnabled;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
|
// before remove we need to remove the local (transactional) natural id cross-reference
|
||||||
|
naturalIdValues = session.getPersistenceContext().getNaturalIdHelper().removeLocalNaturalIdCrossReference(
|
||||||
|
getPersister(),
|
||||||
|
getId(),
|
||||||
|
state
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,6 +118,8 @@ public final class EntityDeleteAction extends EntityAction {
|
||||||
persister.getCacheAccessStrategy().remove( ck );
|
persister.getCacheAccessStrategy().remove( ck );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persistenceContext.getNaturalIdHelper().removeSharedNaturalIdCrossReference( persister, id, naturalIdValues );
|
||||||
|
|
||||||
postDelete();
|
postDelete();
|
||||||
|
|
||||||
if ( getSession().getFactory().getStatistics().isStatisticsEnabled() && !veto ) {
|
if ( getSession().getFactory().getStatistics().isStatisticsEnabled() && !veto ) {
|
||||||
|
|
|
@ -89,7 +89,7 @@ public final class EntityInsertAction extends AbstractEntityInsertAction {
|
||||||
|
|
||||||
EntityEntry entry = session.getPersistenceContext().getEntry( instance );
|
EntityEntry entry = session.getPersistenceContext().getEntry( instance );
|
||||||
if ( entry == null ) {
|
if ( entry == null ) {
|
||||||
throw new AssertionFailure( "possible nonthreadsafe access to session" );
|
throw new AssertionFailure( "possible non-threadsafe access to session" );
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.postInsert( getState() );
|
entry.postInsert( getState() );
|
||||||
|
@ -125,9 +125,10 @@ public final class EntityInsertAction extends AbstractEntityInsertAction {
|
||||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
factory.getStatisticsImplementor().secondLevelCachePut( getPersister().getCacheAccessStrategy().getRegion().getName() );
|
factory.getStatisticsImplementor().secondLevelCachePut( getPersister().getCacheAccessStrategy().getRegion().getName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNaturalIdPostSaveNotifications();
|
||||||
|
|
||||||
postInsert();
|
postInsert();
|
||||||
|
|
||||||
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {
|
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.hibernate.cache.spi.CacheKey;
|
||||||
import org.hibernate.cache.spi.access.SoftLock;
|
import org.hibernate.cache.spi.access.SoftLock;
|
||||||
import org.hibernate.cache.spi.entry.CacheEntry;
|
import org.hibernate.cache.spi.entry.CacheEntry;
|
||||||
import org.hibernate.engine.internal.Versioning;
|
import org.hibernate.engine.internal.Versioning;
|
||||||
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
@ -76,6 +77,14 @@ public final class EntityUpdateAction extends EntityAction {
|
||||||
this.dirtyFields = dirtyProperties;
|
this.dirtyFields = dirtyProperties;
|
||||||
this.hasDirtyCollection = hasDirtyCollection;
|
this.hasDirtyCollection = hasDirtyCollection;
|
||||||
this.rowId = rowId;
|
this.rowId = rowId;
|
||||||
|
|
||||||
|
session.getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
state,
|
||||||
|
previousState,
|
||||||
|
CachedNaturalIdValueSource.UPDATE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -174,6 +183,14 @@ public final class EntityUpdateAction extends EntityAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.getPersistenceContext().getNaturalIdHelper().manageSharedNaturalIdCrossReference(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
state,
|
||||||
|
previousState,
|
||||||
|
CachedNaturalIdValueSource.UPDATE
|
||||||
|
);
|
||||||
|
|
||||||
postUpdate();
|
postUpdate();
|
||||||
|
|
||||||
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {
|
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {
|
||||||
|
|
|
@ -24,28 +24,30 @@
|
||||||
package org.hibernate.engine.internal;
|
package org.hibernate.engine.internal;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.hibernate.pretty.MessageHelper;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
import org.hibernate.AssertionFailure;
|
||||||
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
||||||
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||||
import org.hibernate.cache.spi.access.SoftLock;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.event.spi.EventSource;
|
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintains a {@link org.hibernate.engine.spi.PersistenceContext}-level 2-way cross-reference (xref) between the
|
* Maintains a {@link org.hibernate.engine.spi.PersistenceContext}-level 2-way cross-reference (xref) between the
|
||||||
* identifiers and natural ids of entities associated with the PersistenceContext. Additionally coordinates
|
* identifiers and natural ids of entities associated with the PersistenceContext.
|
||||||
* actions related to the shared caching of the entity's natural id.
|
* <p/>
|
||||||
|
* Most operations resolve the proper {@link NaturalIdResolutionCache} to use based on the persister and
|
||||||
|
* simply delegate calls there.
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -59,132 +61,49 @@ public class NaturalIdXrefDelegate {
|
||||||
this.persistenceContext = persistenceContext;
|
this.persistenceContext = persistenceContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to the session (via the PersistenceContext) to which this delegate ultimately belongs.
|
||||||
|
*
|
||||||
|
* @return The session
|
||||||
|
*/
|
||||||
protected SessionImplementor session() {
|
protected SessionImplementor session() {
|
||||||
return persistenceContext.getSession();
|
return persistenceContext.getSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cacheNaturalIdResolution(
|
/**
|
||||||
EntityPersister persister,
|
* Creates needed cross-reference entries between the given primary (pk) and natural (naturalIdValues) key values
|
||||||
final Serializable pk,
|
* for the given persister. Returns an indication of whether entries were actually made. If those values already
|
||||||
Object[] naturalIdValues,
|
* existed as an entry, {@code false} would be returned here.
|
||||||
CachedNaturalIdValueSource valueSource) {
|
*
|
||||||
persister = locatePersisterForKey( persister );
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param pk The primary key value
|
||||||
|
* @param naturalIdValues The natural id value(s)
|
||||||
|
*
|
||||||
|
* @return {@code true} if a new entry was actually added; {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean cacheNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
|
||||||
validateNaturalId( persister, naturalIdValues );
|
validateNaturalId( persister, naturalIdValues );
|
||||||
|
|
||||||
Object[] previousNaturalIdValues = valueSource == CachedNaturalIdValueSource.UPDATE
|
|
||||||
? persistenceContext.getNaturalIdSnapshot( pk, persister )
|
|
||||||
: null;
|
|
||||||
|
|
||||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
if ( entityNaturalIdResolutionCache == null ) {
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
|
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
|
||||||
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
||||||
}
|
}
|
||||||
|
return entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
|
||||||
final boolean justAddedToLocalCache = entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
|
|
||||||
|
|
||||||
// If second-level caching is enabled cache the resolution there as well
|
|
||||||
// NOTE : the checks using 'justAddedToLocalCache' below protect only the stat journaling, not actually
|
|
||||||
// putting into the shared cache. we still put into the shared cache because that might have locking
|
|
||||||
// semantics that we need to honor. we protect the stat journaling in this manner because there
|
|
||||||
// are cases where we have this method called multiple times and we want to avoid the multiple 'put'
|
|
||||||
// stats incrementing.
|
|
||||||
if ( persister.hasNaturalIdCache() ) {
|
|
||||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
|
||||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session() );
|
|
||||||
|
|
||||||
final SessionFactoryImplementor factory = session().getFactory();
|
|
||||||
|
|
||||||
switch ( valueSource ) {
|
|
||||||
case LOAD: {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
|
||||||
naturalIdCacheKey,
|
|
||||||
pk,
|
|
||||||
session().getTimestamp(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) {
|
|
||||||
factory.getStatisticsImplementor()
|
|
||||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case INSERT: {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, pk );
|
|
||||||
if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) {
|
|
||||||
factory.getStatisticsImplementor()
|
|
||||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
( (EventSource) session() ).getActionQueue().registerProcess(
|
|
||||||
new AfterTransactionCompletionProcess() {
|
|
||||||
@Override
|
|
||||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, pk );
|
|
||||||
|
|
||||||
if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) {
|
|
||||||
factory.getStatisticsImplementor()
|
|
||||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UPDATE: {
|
|
||||||
final NaturalIdCacheKey previousCacheKey = new NaturalIdCacheKey( previousNaturalIdValues, persister, session() );
|
|
||||||
final SoftLock removalLock = naturalIdCacheAccessStrategy.lockItem( previousCacheKey, null );
|
|
||||||
naturalIdCacheAccessStrategy.remove( previousCacheKey );
|
|
||||||
|
|
||||||
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.update( naturalIdCacheKey, pk );
|
|
||||||
if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) {
|
|
||||||
factory.getStatisticsImplementor()
|
|
||||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
( (EventSource) session() ).getActionQueue().registerProcess(
|
|
||||||
new AfterTransactionCompletionProcess() {
|
|
||||||
@Override
|
|
||||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
|
||||||
naturalIdCacheAccessStrategy.unlockRegion( removalLock );
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, pk, lock );
|
|
||||||
|
|
||||||
if ( put && justAddedToLocalCache && factory.getStatistics().isStatisticsEnabled() ) {
|
|
||||||
factory.getStatisticsImplementor()
|
|
||||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
naturalIdCacheAccessStrategy.unlockItem( naturalIdCacheKey, lock );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EntityPersister locatePersisterForKey(EntityPersister persister) {
|
/**
|
||||||
return persistenceContext.getSession().getFactory().getEntityPersister( persister.getRootEntityName() );
|
* Handle removing cross reference entries for the given natural-id/pk combo
|
||||||
}
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
protected void validateNaturalId(EntityPersister persister, Object[] naturalIdValues) {
|
* @param pk The primary key value
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
* @param naturalIdValues The natural id value(s)
|
||||||
throw new IllegalArgumentException( "Entity did not define a natrual-id" );
|
*
|
||||||
}
|
* @return The cached values, if any. May be different from incoming values.
|
||||||
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
|
*/
|
||||||
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
|
public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] deletedNaturalIdValues) {
|
|
||||||
persister = locatePersisterForKey( persister );
|
persister = locatePersisterForKey( persister );
|
||||||
validateNaturalId( persister, deletedNaturalIdValues );
|
validateNaturalId( persister, naturalIdValues );
|
||||||
|
|
||||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
Object[] sessionCachedNaturalIdValues = null;
|
Object[] sessionCachedNaturalIdValues = null;
|
||||||
|
@ -200,17 +119,72 @@ public class NaturalIdXrefDelegate {
|
||||||
if ( persister.hasNaturalIdCache() ) {
|
if ( persister.hasNaturalIdCache() ) {
|
||||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
|
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
|
||||||
.getNaturalIdCacheAccessStrategy();
|
.getNaturalIdCacheAccessStrategy();
|
||||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( deletedNaturalIdValues, persister, session() );
|
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session() );
|
||||||
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||||
|
|
||||||
if ( sessionCachedNaturalIdValues != null
|
if ( sessionCachedNaturalIdValues != null
|
||||||
&& !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
|
&& !Arrays.equals( sessionCachedNaturalIdValues, naturalIdValues ) ) {
|
||||||
final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session() );
|
final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session() );
|
||||||
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sessionCachedNaturalIdValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are the naturals id values cached here (if any) for the given persister+pk combo the same as the given values?
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param pk The primary key value
|
||||||
|
* @param naturalIdValues The natural id value(s) to check
|
||||||
|
*
|
||||||
|
* @return {@code true} if the given naturalIdValues match the current cached values; {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean sameAsCached(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
|
||||||
|
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
|
return entityNaturalIdResolutionCache != null
|
||||||
|
&& entityNaturalIdResolutionCache.sameAsCached( pk, naturalIdValues );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is only valid to define natural ids at the root of an entity hierarchy. This method makes sure we are
|
||||||
|
* using the root persister.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
*
|
||||||
|
* @return The root persister.
|
||||||
|
*/
|
||||||
|
protected EntityPersister locatePersisterForKey(EntityPersister persister) {
|
||||||
|
return persistenceContext.getSession().getFactory().getEntityPersister( persister.getRootEntityName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invariant validate of the natural id. Checks include<ul>
|
||||||
|
* <li>that the entity defines a natural id</li>
|
||||||
|
* <li>the number of natural id values matches the expected number</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param naturalIdValues The natural id values
|
||||||
|
*/
|
||||||
|
protected void validateNaturalId(EntityPersister persister, Object[] naturalIdValues) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
throw new IllegalArgumentException( "Entity did not define a natrual-id" );
|
||||||
|
}
|
||||||
|
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
|
||||||
|
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a persister and primary key, find the locally cross-referenced natural id.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param pk The entity primary key
|
||||||
|
*
|
||||||
|
* @return The corresponding cross-referenced natural id values, or {@code null} if none
|
||||||
|
*/
|
||||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
||||||
persister = locatePersisterForKey( persister );
|
persister = locatePersisterForKey( persister );
|
||||||
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
|
@ -226,6 +200,18 @@ public class NaturalIdXrefDelegate {
|
||||||
return cachedNaturalId.getValues();
|
return cachedNaturalId.getValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a persister and natural-id value(s), find the locally cross-referenced primary key. Will return
|
||||||
|
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE} if the given natural ids are known to
|
||||||
|
* be invalid (see {@link #stashInvalidNaturalIdReference}).
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param naturalIdValues The natural id value(s)
|
||||||
|
*
|
||||||
|
* @return The corresponding cross-referenced primary key,
|
||||||
|
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE},
|
||||||
|
* or {@code null} if none
|
||||||
|
*/
|
||||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
||||||
persister = locatePersisterForKey( persister );
|
persister = locatePersisterForKey( persister );
|
||||||
validateNaturalId( persister, naturalIdValues );
|
validateNaturalId( persister, naturalIdValues );
|
||||||
|
@ -249,6 +235,11 @@ public class NaturalIdXrefDelegate {
|
||||||
|
|
||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we did not find a hit, see if we know about these natural ids as invalid...
|
||||||
|
if ( entityNaturalIdResolutionCache.containsInvalidNaturalIdReference( naturalIdValues ) ) {
|
||||||
|
return PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session cache miss, see if second-level caching is enabled
|
// Session cache miss, see if second-level caching is enabled
|
||||||
|
@ -296,7 +287,68 @@ public class NaturalIdXrefDelegate {
|
||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all locally cross-referenced primary keys for the given persister. Used as part of load
|
||||||
|
* synchronization process.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
*
|
||||||
|
* @return The primary keys
|
||||||
|
*
|
||||||
|
* @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled
|
||||||
|
*/
|
||||||
|
public Collection<Serializable> getCachedPkResolutions(EntityPersister persister) {
|
||||||
|
persister = locatePersisterForKey( persister );
|
||||||
|
|
||||||
|
Collection<Serializable> pks = null;
|
||||||
|
|
||||||
|
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
|
if ( entityNaturalIdResolutionCache != null ) {
|
||||||
|
pks = entityNaturalIdResolutionCache.pkToNaturalIdMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( pks == null || pks.isEmpty() ) {
|
||||||
|
return java.util.Collections.emptyList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return java.util.Collections.unmodifiableCollection( pks );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As part of "load synchronization process", if a particular natural id is found to have changed we need to track
|
||||||
|
* its invalidity until after the next flush. This method lets the "load synchronization process" indicate
|
||||||
|
* when it has encountered such changes.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param invalidNaturalIdValues The "old" natural id values.
|
||||||
|
*
|
||||||
|
* @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled
|
||||||
|
*/
|
||||||
|
public void stashInvalidNaturalIdReference(EntityPersister persister, Object[] invalidNaturalIdValues) {
|
||||||
|
persister = locatePersisterForKey( persister );
|
||||||
|
|
||||||
|
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||||
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
|
throw new AssertionFailure( "Expecting NaturalIdResolutionCache to exist already for entity " + persister.getEntityName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
entityNaturalIdResolutionCache.stashInvalidNaturalIdReference( invalidNaturalIdValues );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Again, as part of "load synchronization process" we need to also be able to clear references to these
|
||||||
|
* known-invalid natural-ids after flush. This method exposes that capability.
|
||||||
|
*/
|
||||||
|
public void unStashInvalidNaturalIdReferences() {
|
||||||
|
for ( NaturalIdResolutionCache naturalIdResolutionCache : naturalIdResolutionCacheMap.values() ) {
|
||||||
|
naturalIdResolutionCache.unStashInvalidNaturalIdReferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to put natural id values into collections. Useful mainly to apply equals/hashCode implementations.
|
||||||
|
*/
|
||||||
private static class CachedNaturalId {
|
private static class CachedNaturalId {
|
||||||
private final EntityPersister persister;
|
private final EntityPersister persister;
|
||||||
private final Object[] values;
|
private final Object[] values;
|
||||||
|
@ -347,10 +399,10 @@ public class NaturalIdXrefDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
final CachedNaturalId other = (CachedNaturalId) obj;
|
final CachedNaturalId other = (CachedNaturalId) obj;
|
||||||
return persister.equals( other.persister ) && areSame( values, other.values );
|
return persister.equals( other.persister ) && isSame(other.values );
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areSame(Object[] values, Object[] otherValues) {
|
private boolean isSame(Object[] otherValues) {
|
||||||
// lengths have already been verified at this point
|
// lengths have already been verified at this point
|
||||||
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
|
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
|
||||||
if ( ! naturalIdTypes[i].isEqual( values[i], otherValues[i], persister.getFactory() ) ) {
|
if ( ! naturalIdTypes[i].isEqual( values[i], otherValues[i], persister.getFactory() ) ) {
|
||||||
|
@ -361,6 +413,9 @@ public class NaturalIdXrefDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the persister-specific cross-reference cache.
|
||||||
|
*/
|
||||||
private static class NaturalIdResolutionCache implements Serializable {
|
private static class NaturalIdResolutionCache implements Serializable {
|
||||||
private final EntityPersister persister;
|
private final EntityPersister persister;
|
||||||
private final Type[] naturalIdTypes;
|
private final Type[] naturalIdTypes;
|
||||||
|
@ -368,6 +423,8 @@ public class NaturalIdXrefDelegate {
|
||||||
private Map<Serializable, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, CachedNaturalId>();
|
private Map<Serializable, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, CachedNaturalId>();
|
||||||
private Map<CachedNaturalId, Serializable> naturalIdToPkMap = new ConcurrentHashMap<CachedNaturalId, Serializable>();
|
private Map<CachedNaturalId, Serializable> naturalIdToPkMap = new ConcurrentHashMap<CachedNaturalId, Serializable>();
|
||||||
|
|
||||||
|
private List<CachedNaturalId> invalidNaturalIdList;
|
||||||
|
|
||||||
private NaturalIdResolutionCache(EntityPersister persister) {
|
private NaturalIdResolutionCache(EntityPersister persister) {
|
||||||
this.persister = persister;
|
this.persister = persister;
|
||||||
|
|
||||||
|
@ -383,10 +440,20 @@ public class NaturalIdXrefDelegate {
|
||||||
return persister;
|
return persister;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean sameAsCached(Serializable pk, Object[] naturalIdValues) {
|
||||||
|
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
|
||||||
|
if ( initial != null ) {
|
||||||
|
if ( initial.isSame( naturalIdValues ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean cache(Serializable pk, Object[] naturalIdValues) {
|
public boolean cache(Serializable pk, Object[] naturalIdValues) {
|
||||||
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
|
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
|
||||||
if ( initial != null ) {
|
if ( initial != null ) {
|
||||||
if ( areSame( naturalIdValues, initial.getValues() ) ) {
|
if ( initial.isSame( naturalIdValues ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
naturalIdToPkMap.remove( initial );
|
naturalIdToPkMap.remove( initial );
|
||||||
|
@ -399,14 +466,22 @@ public class NaturalIdXrefDelegate {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areSame(Object[] naturalIdValues, Object[] values) {
|
public void stashInvalidNaturalIdReference(Object[] invalidNaturalIdValues) {
|
||||||
// lengths have already been verified at this point
|
if ( invalidNaturalIdList == null ) {
|
||||||
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
|
invalidNaturalIdList = new ArrayList<CachedNaturalId>();
|
||||||
if ( ! naturalIdTypes[i].isEqual( naturalIdValues[i], values[i] ) ) {
|
}
|
||||||
return false;
|
invalidNaturalIdList.add( new CachedNaturalId( persister, invalidNaturalIdValues ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean containsInvalidNaturalIdReference(Object[] naturalIdValues) {
|
||||||
|
return invalidNaturalIdList != null
|
||||||
|
&& invalidNaturalIdList.contains( new CachedNaturalId( persister, naturalIdValues ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unStashInvalidNaturalIdReferences() {
|
||||||
|
if ( invalidNaturalIdList != null ) {
|
||||||
|
invalidNaturalIdList.clear();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
|
@ -47,6 +48,10 @@ import org.hibernate.MappingException;
|
||||||
import org.hibernate.NonUniqueObjectException;
|
import org.hibernate.NonUniqueObjectException;
|
||||||
import org.hibernate.PersistentObjectException;
|
import org.hibernate.PersistentObjectException;
|
||||||
import org.hibernate.TransientObjectException;
|
import org.hibernate.TransientObjectException;
|
||||||
|
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
||||||
|
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
||||||
|
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||||
|
import org.hibernate.cache.spi.access.SoftLock;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
import org.hibernate.engine.loading.internal.LoadContexts;
|
import org.hibernate.engine.loading.internal.LoadContexts;
|
||||||
import org.hibernate.engine.spi.AssociationKey;
|
import org.hibernate.engine.spi.AssociationKey;
|
||||||
|
@ -58,8 +63,10 @@ import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.engine.spi.Status;
|
import org.hibernate.engine.spi.Status;
|
||||||
|
import org.hibernate.event.spi.EventSource;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.MarkerObject;
|
import org.hibernate.internal.util.MarkerObject;
|
||||||
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
||||||
|
@ -313,8 +320,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persister = locateProperPersister( persister );
|
||||||
|
|
||||||
// let's first see if it is part of the natural id cache...
|
// let's first see if it is part of the natural id cache...
|
||||||
final Object[] cachedValue = findCachedNaturalId( persister, id );
|
final Object[] cachedValue = naturalIdHelper.findCachedNaturalId( persister, id );
|
||||||
if ( cachedValue != null ) {
|
if ( cachedValue != null ) {
|
||||||
return cachedValue;
|
return cachedValue;
|
||||||
}
|
}
|
||||||
|
@ -323,7 +332,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
||||||
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
|
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
|
||||||
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
||||||
cacheNaturalIdResolution( persister, id, dbValue, CachedNaturalIdValueSource.LOAD );
|
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
dbValue
|
||||||
|
);
|
||||||
return dbValue;
|
return dbValue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -339,11 +352,19 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
for ( int i = 0; i < props.length; i++ ) {
|
for ( int i = 0; i < props.length; i++ ) {
|
||||||
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
|
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
|
||||||
}
|
}
|
||||||
cacheNaturalIdResolution( persister, id, naturalIdSnapshotSubSet, CachedNaturalIdValueSource.LOAD );
|
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
naturalIdSnapshotSubSet
|
||||||
|
);
|
||||||
return naturalIdSnapshotSubSet;
|
return naturalIdSnapshotSubSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntityPersister locateProperPersister(EntityPersister persister) {
|
||||||
|
return session.getFactory().getEntityPersister( persister.getRootEntityName() );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cached database snapshot for the requested entity key.
|
* Retrieve the cached database snapshot for the requested entity key.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
@ -1091,7 +1112,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFlushing(boolean flushing) {
|
public void setFlushing(boolean flushing) {
|
||||||
|
final boolean afterFlush = this.flushing && ! flushing;
|
||||||
this.flushing = flushing;
|
this.flushing = flushing;
|
||||||
|
if ( afterFlush ) {
|
||||||
|
getNaturalIdHelper().cleanupFromSynchronizations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1699,58 +1724,293 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
|
|
||||||
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
private NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
|
private final NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
|
||||||
|
|
||||||
@Override
|
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
|
||||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state) {
|
@Override
|
||||||
final EntityPersister persister = entityEntry.getPersister();
|
public void cacheNaturalIdCrossReferenceFromLoad(
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
EntityPersister persister,
|
||||||
// nothing to do
|
Serializable id,
|
||||||
return;
|
Object[] naturalIdValues) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
persister = locateProperPersister( persister );
|
||||||
|
|
||||||
|
// 'justAddedLocally' is meant to handle the case where we would get double stats jounaling
|
||||||
|
// from a single load event. The first put journal would come from the natural id resolution;
|
||||||
|
// the second comes from the entity loading. In this condition, we want to avoid the multiple
|
||||||
|
// 'put' stats incrementing.
|
||||||
|
boolean justAddedLocally = naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues );
|
||||||
|
|
||||||
|
if ( justAddedLocally && persister.hasNaturalIdCache() ) {
|
||||||
|
managedSharedCacheEntries( persister, id, naturalIdValues, CachedNaturalIdValueSource.LOAD );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
@Override
|
||||||
|
public void manageLocalNaturalIdCrossReference(
|
||||||
|
EntityPersister persister,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
Object[] previousState,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// cache
|
persister = locateProperPersister( persister );
|
||||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
|
||||||
persister,
|
|
||||||
entityEntry.getId(),
|
|
||||||
naturalIdValues,
|
|
||||||
CachedNaturalIdValueSource.INSERT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// cache
|
||||||
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state) {
|
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues );
|
||||||
final EntityPersister persister = entityEntry.getPersister();
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
@Override
|
||||||
|
public void manageSharedNaturalIdCrossReference(
|
||||||
|
EntityPersister persister,
|
||||||
|
final Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
Object[] previousState,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// re-cache
|
if ( !persister.hasNaturalIdCache() ) {
|
||||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
// nothing to do
|
||||||
persister,
|
return;
|
||||||
entityEntry.getId(),
|
}
|
||||||
naturalIdValues,
|
|
||||||
CachedNaturalIdValueSource.UPDATE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
persister = locateProperPersister( persister );
|
||||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState) {
|
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
|
||||||
final EntityPersister persister = entityEntry.getPersister();
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
managedSharedCacheEntries( persister, id, naturalIdValues, source );
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object[] naturalIdValues = getNaturalIdValues( deletedState, persister );
|
private void managedSharedCacheEntries(
|
||||||
|
EntityPersister persister,
|
||||||
|
final Serializable id,
|
||||||
|
Object[] naturalIdValues,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
|
||||||
|
|
||||||
// evict from cache
|
final SessionFactoryImplementor factory = session.getFactory();
|
||||||
naturalIdXrefDelegate.evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
|
|
||||||
|
switch ( source ) {
|
||||||
|
case LOAD: {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
||||||
|
naturalIdCacheKey,
|
||||||
|
id,
|
||||||
|
session.getTimestamp(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
|
factory.getStatisticsImplementor().naturalIdCachePut(
|
||||||
|
naturalIdCacheAccessStrategy.getRegion()
|
||||||
|
.getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INSERT: {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, id );
|
||||||
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
|
factory.getStatisticsImplementor()
|
||||||
|
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
( (EventSource) session ).getActionQueue().registerProcess(
|
||||||
|
new AfterTransactionCompletionProcess() {
|
||||||
|
@Override
|
||||||
|
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, id );
|
||||||
|
|
||||||
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
|
factory.getStatisticsImplementor()
|
||||||
|
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UPDATE: {
|
||||||
|
final Object[] previousNaturalIdValues = getNaturalIdSnapshot( id, persister );
|
||||||
|
final NaturalIdCacheKey previousCacheKey = new NaturalIdCacheKey( previousNaturalIdValues, persister, session );
|
||||||
|
final SoftLock removalLock = naturalIdCacheAccessStrategy.lockItem( previousCacheKey, null );
|
||||||
|
naturalIdCacheAccessStrategy.remove( previousCacheKey );
|
||||||
|
|
||||||
|
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.update( naturalIdCacheKey, id );
|
||||||
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
|
factory.getStatisticsImplementor()
|
||||||
|
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
( (EventSource) session ).getActionQueue().registerProcess(
|
||||||
|
new AfterTransactionCompletionProcess() {
|
||||||
|
@Override
|
||||||
|
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||||
|
naturalIdCacheAccessStrategy.unlockRegion( removalLock );
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, id, lock );
|
||||||
|
|
||||||
|
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
|
factory.getStatisticsImplementor()
|
||||||
|
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
naturalIdCacheAccessStrategy.unlockItem( naturalIdCacheKey, lock );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] state) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
persister = locateProperPersister( persister );
|
||||||
|
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
||||||
|
|
||||||
|
final Object[] localNaturalIdValues = naturalIdXrefDelegate.removeNaturalIdCrossReference(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
naturalIdValues
|
||||||
|
);
|
||||||
|
|
||||||
|
return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSharedNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] naturalIdValues) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! persister.hasNaturalIdCache() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo : couple of things wrong here:
|
||||||
|
// 1) should be using access strategy, not plain evict..
|
||||||
|
// 2) should prefer session-cached values if any (requires interaction from removeLocalNaturalIdCrossReference
|
||||||
|
|
||||||
|
persister = locateProperPersister( persister );
|
||||||
|
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
|
||||||
|
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||||
|
|
||||||
|
// if ( sessionCachedNaturalIdValues != null
|
||||||
|
// && !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
|
||||||
|
// final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session );
|
||||||
|
// naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
||||||
|
return naturalIdXrefDelegate.findCachedNaturalId( locateProperPersister( persister ), pk );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
||||||
|
return naturalIdXrefDelegate.findCachedNaturalIdResolution( locateProperPersister( persister ), naturalIdValues );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister) {
|
||||||
|
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
|
||||||
|
if ( state.length == naturalIdPropertyIndexes.length ) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object[] naturalIdValues = new Object[naturalIdPropertyIndexes.length];
|
||||||
|
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
|
||||||
|
naturalIdValues[i] = state[naturalIdPropertyIndexes[i]];
|
||||||
|
}
|
||||||
|
return naturalIdValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] extractNaturalIdValues(Object entity, EntityPersister persister) {
|
||||||
|
if ( entity == null ) {
|
||||||
|
throw new AssertionFailure( "Entity from which to extract natural id value(s) cannot be null" );
|
||||||
|
}
|
||||||
|
if ( persister == null ) {
|
||||||
|
throw new AssertionFailure( "Persister to use in extracting natural id value(s) cannot be null" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties();
|
||||||
|
final Object[] naturalIdValues = new Object[naturalIdentifierProperties.length];
|
||||||
|
|
||||||
|
for ( int i = 0; i < naturalIdentifierProperties.length; i++ ) {
|
||||||
|
naturalIdValues[i] = persister.getPropertyValue( entity, naturalIdentifierProperties[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return naturalIdValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Serializable> getCachedPkResolutions(EntityPersister entityPersister) {
|
||||||
|
return naturalIdXrefDelegate.getCachedPkResolutions( entityPersister );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSynchronization(EntityPersister persister, Serializable pk, Object entity) {
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
persister = locateProperPersister( persister );
|
||||||
|
|
||||||
|
final Object[] naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister );
|
||||||
|
final boolean changed = ! naturalIdXrefDelegate.sameAsCached(
|
||||||
|
persister,
|
||||||
|
pk,
|
||||||
|
naturalIdValuesFromCurrentObjectState
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( changed ) {
|
||||||
|
final Object[] cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
|
||||||
|
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, pk, naturalIdValuesFromCurrentObjectState );
|
||||||
|
naturalIdXrefDelegate.stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
|
||||||
|
|
||||||
|
removeSharedNaturalIdCrossReference(
|
||||||
|
persister,
|
||||||
|
pk,
|
||||||
|
cachedNaturalIdValues
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupFromSynchronizations() {
|
||||||
|
naturalIdXrefDelegate.unStashInvalidNaturalIdReferences();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NaturalIdHelper getNaturalIdHelper() {
|
||||||
|
return naturalIdHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object[] getNaturalIdValues(Object[] state, EntityPersister persister) {
|
private Object[] getNaturalIdValues(Object[] state, EntityPersister persister) {
|
||||||
|
@ -1763,24 +2023,4 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
|
|
||||||
return naturalIdValues;
|
return naturalIdValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
|
||||||
return naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
|
||||||
return naturalIdXrefDelegate.findCachedNaturalIdResolution( persister, naturalIdValues );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cacheNaturalIdResolution(
|
|
||||||
EntityPersister persister,
|
|
||||||
final Serializable pk,
|
|
||||||
Object[] naturalIdValues,
|
|
||||||
CachedNaturalIdValueSource valueSource) {
|
|
||||||
naturalIdXrefDelegate.cacheNaturalIdResolution( persister, pk, naturalIdValues, valueSource );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,23 +124,41 @@ public final class TwoPhaseLoad {
|
||||||
final SessionImplementor session,
|
final SessionImplementor session,
|
||||||
final PreLoadEvent preLoadEvent,
|
final PreLoadEvent preLoadEvent,
|
||||||
final PostLoadEvent postLoadEvent) throws HibernateException {
|
final PostLoadEvent postLoadEvent) throws HibernateException {
|
||||||
|
|
||||||
//TODO: Should this be an InitializeEntityEventListener??? (watch out for performance!)
|
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||||
EntityEntry entityEntry = persistenceContext.getEntry(entity);
|
final EntityEntry entityEntry = persistenceContext.getEntry(entity);
|
||||||
|
final EntityPersister persister = entityEntry.getPersister();
|
||||||
|
final Serializable id = entityEntry.getId();
|
||||||
|
|
||||||
|
// persistenceContext.getNaturalIdHelper().startingLoad( persister, id );
|
||||||
|
// try {
|
||||||
|
doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, postLoadEvent );
|
||||||
|
// }
|
||||||
|
// finally {
|
||||||
|
// persistenceContext.getNaturalIdHelper().endingLoad( persister, id );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doInitializeEntity(
|
||||||
|
final Object entity,
|
||||||
|
final EntityEntry entityEntry,
|
||||||
|
final boolean readOnly,
|
||||||
|
final SessionImplementor session,
|
||||||
|
final PreLoadEvent preLoadEvent,
|
||||||
|
final PostLoadEvent postLoadEvent) throws HibernateException {
|
||||||
if ( entityEntry == null ) {
|
if ( entityEntry == null ) {
|
||||||
throw new AssertionFailure( "possible non-threadsafe access to the session" );
|
throw new AssertionFailure( "possible non-threadsafe access to the session" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||||
EntityPersister persister = entityEntry.getPersister();
|
EntityPersister persister = entityEntry.getPersister();
|
||||||
Serializable id = entityEntry.getId();
|
Serializable id = entityEntry.getId();
|
||||||
Object[] hydratedState = entityEntry.getLoadedState();
|
Object[] hydratedState = entityEntry.getLoadedState();
|
||||||
|
|
||||||
if ( LOG.isDebugEnabled() ) {
|
if ( LOG.isDebugEnabled() ) {
|
||||||
LOG.debugf(
|
LOG.debugf(
|
||||||
"Resolving associations for %s",
|
"Resolving associations for %s",
|
||||||
MessageHelper.infoString( persister, id, session.getFactory() )
|
MessageHelper.infoString( persister, id, session.getFactory() )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type[] types = persister.getPropertyTypes();
|
Type[] types = persister.getPropertyTypes();
|
||||||
|
@ -172,9 +190,9 @@ public final class TwoPhaseLoad {
|
||||||
|
|
||||||
if ( LOG.isDebugEnabled() ) {
|
if ( LOG.isDebugEnabled() ) {
|
||||||
LOG.debugf(
|
LOG.debugf(
|
||||||
"Adding entity to second-level cache: %s",
|
"Adding entity to second-level cache: %s",
|
||||||
MessageHelper.infoString( persister, id, session.getFactory() )
|
MessageHelper.infoString( persister, id, session.getFactory() )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object version = Versioning.getVersion(hydratedState, persister);
|
Object version = Versioning.getVersion(hydratedState, persister);
|
||||||
|
@ -217,6 +235,14 @@ public final class TwoPhaseLoad {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( persister.hasNaturalIdentifier() ) {
|
||||||
|
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
persistenceContext.getNaturalIdHelper().extractNaturalIdValues( hydratedState, persister )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
boolean isReallyReadOnly = readOnly;
|
boolean isReallyReadOnly = readOnly;
|
||||||
if ( !persister.isMutable() ) {
|
if ( !persister.isMutable() ) {
|
||||||
isReallyReadOnly = true;
|
isReallyReadOnly = true;
|
||||||
|
@ -252,7 +278,7 @@ public final class TwoPhaseLoad {
|
||||||
entity,
|
entity,
|
||||||
entityEntry.isLoadedWithLazyPropertiesUnfetched(),
|
entityEntry.isLoadedWithLazyPropertiesUnfetched(),
|
||||||
session
|
session
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( session.isEventSource() ) {
|
if ( session.isEventSource() ) {
|
||||||
postLoadEvent.setEntity( entity ).setId( id ).setPersister( persister );
|
postLoadEvent.setEntity( entity ).setId( id ).setPersister( persister );
|
||||||
|
@ -277,7 +303,6 @@ public final class TwoPhaseLoad {
|
||||||
if ( factory.getStatistics().isStatisticsEnabled() ) {
|
if ( factory.getStatistics().isStatisticsEnabled() ) {
|
||||||
factory.getStatisticsImplementor().loadEntity( persister.getEntityName() );
|
factory.getStatisticsImplementor().loadEntity( persister.getEntityName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean useMinimalPuts(SessionImplementor session, EntityEntry entityEntry) {
|
private static boolean useMinimalPuts(SessionImplementor session, EntityEntry entityEntry) {
|
||||||
|
|
|
@ -240,8 +240,6 @@ public final class EntityEntry implements Serializable {
|
||||||
.getFactory()
|
.getFactory()
|
||||||
.getCustomEntityDirtinessStrategy()
|
.getCustomEntityDirtinessStrategy()
|
||||||
.resetDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
.resetDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
|
||||||
|
|
||||||
notifyLoadedStateUpdated( updatedState );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -252,8 +250,6 @@ public final class EntityEntry implements Serializable {
|
||||||
previousStatus = status;
|
previousStatus = status;
|
||||||
status = Status.GONE;
|
status = Status.GONE;
|
||||||
existsInDatabase = false;
|
existsInDatabase = false;
|
||||||
|
|
||||||
notifyLoadedStateDeleted( deletedState );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,8 +258,6 @@ public final class EntityEntry implements Serializable {
|
||||||
*/
|
*/
|
||||||
public void postInsert(Object[] insertedState) {
|
public void postInsert(Object[] insertedState) {
|
||||||
existsInDatabase = true;
|
existsInDatabase = true;
|
||||||
|
|
||||||
notifyLoadedStateInserted( insertedState );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
|
||||||
|
@ -360,7 +354,13 @@ public final class EntityEntry implements Serializable {
|
||||||
}
|
}
|
||||||
setStatus( Status.MANAGED );
|
setStatus( Status.MANAGED );
|
||||||
loadedState = getPersister().getPropertyValues( entity );
|
loadedState = getPersister().getPropertyValues( entity );
|
||||||
notifyLoadedStateUpdated( loadedState );
|
persistenceContext.getNaturalIdHelper().manageLocalNaturalIdCrossReference(
|
||||||
|
persister,
|
||||||
|
id,
|
||||||
|
loadedState,
|
||||||
|
null,
|
||||||
|
CachedNaturalIdValueSource.LOAD
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,30 +374,6 @@ public final class EntityEntry implements Serializable {
|
||||||
return loadedWithLazyPropertiesUnfetched;
|
return loadedWithLazyPropertiesUnfetched;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyLoadedStateUpdated(Object[] state) {
|
|
||||||
if ( persistenceContext == null ) {
|
|
||||||
throw new HibernateException( "PersistenceContext was null on attempt to update loaded state" );
|
|
||||||
}
|
|
||||||
|
|
||||||
persistenceContext.entityStateUpdatedNotification( this, state );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyLoadedStateInserted(Object[] state) {
|
|
||||||
if ( persistenceContext == null ) {
|
|
||||||
throw new HibernateException( "PersistenceContext was null on attempt to insert loaded state" );
|
|
||||||
}
|
|
||||||
|
|
||||||
persistenceContext.entityStateInsertedNotification( this, state );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyLoadedStateDeleted(Object[] deletedState) {
|
|
||||||
if ( persistenceContext == null ) {
|
|
||||||
throw new HibernateException( "PersistenceContext was null on attempt to delete loaded state" );
|
|
||||||
}
|
|
||||||
|
|
||||||
persistenceContext.entityStateDeletedNotification( this, deletedState );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom serialization routine used during serialization of a
|
* Custom serialization routine used during serialization of a
|
||||||
* Session/PersistenceContext for increased performance.
|
* Session/PersistenceContext for increased performance.
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
package org.hibernate.engine.spi;
|
package org.hibernate.engine.spi;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -684,33 +685,157 @@ public interface PersistenceContext {
|
||||||
public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id);
|
public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used to signal that loaded entity state has changed.
|
* Provides centralized access to natural-id-related functionality.
|
||||||
*
|
|
||||||
* @param entityEntry The entry of the entity that has changed.
|
|
||||||
* @param state The new state.
|
|
||||||
*/
|
*/
|
||||||
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state);
|
public static interface NaturalIdHelper {
|
||||||
|
public static final Serializable INVALID_NATURAL_ID_REFERENCE = new Serializable() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of "full entity state", extract the portions that represent the natural id
|
||||||
|
*
|
||||||
|
* @param state The attribute state array
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
*
|
||||||
|
* @return The extracted natural id values
|
||||||
|
*/
|
||||||
|
public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an entity instance, extract the values that represent the natural id
|
||||||
|
*
|
||||||
|
* @param entity The entity instance
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
*
|
||||||
|
* @return The extracted natural id values
|
||||||
|
*/
|
||||||
|
public Object[] extractNaturalIdValues(Object entity, EntityPersister persister);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs processing related to creating natural-id cross-reference entries on load.
|
||||||
|
* Handles both the local (transactional) and shared (second-level) caches.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param id The primary key value
|
||||||
|
* @param naturalIdValues The natural id values
|
||||||
|
*/
|
||||||
|
public void cacheNaturalIdCrossReferenceFromLoad(
|
||||||
|
EntityPersister persister,
|
||||||
|
Serializable id,
|
||||||
|
Object[] naturalIdValues);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates necessary local cross-reference entries.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param id The primary key value
|
||||||
|
* @param state Generally the "full entity state array", though could also be the natural id values array
|
||||||
|
* @param previousState Generally the "full entity state array", though could also be the natural id values array.
|
||||||
|
* Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE}
|
||||||
|
* @param source Enumeration representing how these values are coming into cache.
|
||||||
|
*/
|
||||||
|
public void manageLocalNaturalIdCrossReference(
|
||||||
|
EntityPersister persister,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
Object[] previousState,
|
||||||
|
CachedNaturalIdValueSource source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up local cross-reference entries.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param id The primary key value
|
||||||
|
* @param state Generally the "full entity state array", though could also be the natural id values array
|
||||||
|
*
|
||||||
|
* @return The local cached natural id values (could be different from given values).
|
||||||
|
*/
|
||||||
|
public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates necessary shared (second level cache) cross-reference entries.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param id The primary key value
|
||||||
|
* @param state Generally the "full entity state array", though could also be the natural id values array
|
||||||
|
* @param previousState Generally the "full entity state array", though could also be the natural id values array.
|
||||||
|
* Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE}
|
||||||
|
* @param source Enumeration representing how these values are coming into cache.
|
||||||
|
*/
|
||||||
|
public void manageSharedNaturalIdCrossReference(
|
||||||
|
EntityPersister persister,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
Object[] previousState,
|
||||||
|
CachedNaturalIdValueSource source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up local cross-reference entries.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param id The primary key value
|
||||||
|
* @param naturalIdValues The natural id values array
|
||||||
|
*/
|
||||||
|
public void removeSharedNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] naturalIdValues);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a persister and primary key, find the corresponding cross-referenced natural id values.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param pk The primary key value
|
||||||
|
*
|
||||||
|
* @return The cross-referenced natural-id values, or {@code null}
|
||||||
|
*/
|
||||||
|
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a persister and natural-id values, find the corresponding cross-referenced primary key. Will return
|
||||||
|
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE} if the given natural ids are known to
|
||||||
|
* be invalid.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param naturalIdValues The natural id value(s)
|
||||||
|
*
|
||||||
|
* @return The corresponding cross-referenced primary key,
|
||||||
|
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE},
|
||||||
|
* or {@code null}.
|
||||||
|
*/
|
||||||
|
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all the locally cached primary key cross-reference entries for the given persister.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
*
|
||||||
|
* @return The primary keys
|
||||||
|
*/
|
||||||
|
public Collection<Serializable> getCachedPkResolutions(EntityPersister persister);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the "load synchronization process". Responsible for maintaining cross-reference entries
|
||||||
|
* when natural-id values were found to have changed. Also responsible for tracking the old values
|
||||||
|
* as no longer valid until the next flush because otherwise going to the database would just re-pull
|
||||||
|
* the old values as valid. In this last responsibility, {@link #cleanupFromSynchronizations} is
|
||||||
|
* the inverse process called after flush to clean up those entries.
|
||||||
|
*
|
||||||
|
* @param persister The persister representing the entity type.
|
||||||
|
* @param pk The primary key
|
||||||
|
* @param entity The entity instance
|
||||||
|
*
|
||||||
|
* @see #cleanupFromSynchronizations
|
||||||
|
*/
|
||||||
|
public void handleSynchronization(EntityPersister persister, Serializable pk, Object entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The clean up process of {@link #handleSynchronization}. Responsible for cleaning up the tracking
|
||||||
|
* of old values as no longer valid.
|
||||||
|
*/
|
||||||
|
public void cleanupFromSynchronizations();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used to signal that entity state has been inserted.
|
* Access to the natural-id helper for this persistence context
|
||||||
*
|
*
|
||||||
* @param entityEntry The entry of the inserted entity
|
* @return This persistence context's natural-id helper
|
||||||
* @param state The new state
|
|
||||||
*/
|
*/
|
||||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state);
|
public NaturalIdHelper getNaturalIdHelper();
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used to signal that entity state has been deleted.
|
|
||||||
*
|
|
||||||
* @param entityEntry The entry of the inserted entity
|
|
||||||
* @param deletedState The state of the entity at the time of deletion
|
|
||||||
*/
|
|
||||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState);
|
|
||||||
|
|
||||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk);
|
|
||||||
|
|
||||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalId);
|
|
||||||
|
|
||||||
public void cacheNaturalIdResolution(EntityPersister persister, Serializable pk, Object[] naturalId, CachedNaturalIdValueSource valueSource);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
||||||
|
|
||||||
final Object[] snapshot = loaded == null
|
final Object[] snapshot = loaded == null
|
||||||
? session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister )
|
? session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister )
|
||||||
: extractNaturalIdValues( loaded, naturalIdentifierPropertiesIndexes );
|
: session.getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( loaded, persister );
|
||||||
|
|
||||||
for ( int i=0; i<naturalIdentifierPropertiesIndexes.length; i++ ) {
|
for ( int i=0; i<naturalIdentifierPropertiesIndexes.length; i++ ) {
|
||||||
final int naturalIdentifierPropertyIndex = naturalIdentifierPropertiesIndexes[i];
|
final int naturalIdentifierPropertyIndex = naturalIdentifierPropertiesIndexes[i];
|
||||||
|
@ -139,15 +139,6 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object[] extractNaturalIdValues(Object[] entitySnapshot, int[] naturalIdPropertyIndexes) {
|
|
||||||
final Object[] naturalIdSnapshotSubSet = new Object[ naturalIdPropertyIndexes.length ];
|
|
||||||
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
|
|
||||||
naturalIdSnapshotSubSet[i] = entitySnapshot[ naturalIdPropertyIndexes[i] ];
|
|
||||||
}
|
|
||||||
return naturalIdSnapshotSubSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes a single entity's state to the database, by scheduling
|
* Flushes a single entity's state to the database, by scheduling
|
||||||
* an update action, if necessary
|
* an update action, if necessary
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.hibernate.cache.spi.access.SoftLock;
|
||||||
import org.hibernate.cache.spi.entry.CacheEntry;
|
import org.hibernate.cache.spi.entry.CacheEntry;
|
||||||
import org.hibernate.engine.internal.TwoPhaseLoad;
|
import org.hibernate.engine.internal.TwoPhaseLoad;
|
||||||
import org.hibernate.engine.internal.Versioning;
|
import org.hibernate.engine.internal.Versioning;
|
||||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
@ -445,18 +444,10 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entity != null && persister.hasNaturalIdentifier()) {
|
if (entity != null && persister.hasNaturalIdentifier()) {
|
||||||
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties();
|
event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
|
||||||
final Object[] naturalId = new Object[naturalIdentifierProperties.length];
|
|
||||||
|
|
||||||
for ( int i = 0; i < naturalIdentifierProperties.length; i++ ) {
|
|
||||||
naturalId[i] = persister.getPropertyValue( entity, naturalIdentifierProperties[i] );
|
|
||||||
}
|
|
||||||
|
|
||||||
event.getSession().getPersistenceContext().cacheNaturalIdResolution(
|
|
||||||
persister,
|
persister,
|
||||||
event.getEntityId(),
|
event.getEntityId(),
|
||||||
naturalId,
|
source.getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( entity, persister )
|
||||||
CachedNaturalIdValueSource.LOAD
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class DefaultResolveNaturalIdEventListener
|
||||||
* @return The entity from the cache, or null.
|
* @return The entity from the cache, or null.
|
||||||
*/
|
*/
|
||||||
protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
|
protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
|
||||||
return event.getSession().getPersistenceContext().findCachedNaturalIdResolution(
|
return event.getSession().getPersistenceContext().getNaturalIdHelper().findCachedNaturalIdResolution(
|
||||||
event.getEntityPersister(),
|
event.getEntityPersister(),
|
||||||
event.getOrderedNaturalIdValues()
|
event.getOrderedNaturalIdValues()
|
||||||
);
|
);
|
||||||
|
@ -142,11 +142,10 @@ public class DefaultResolveNaturalIdEventListener
|
||||||
|
|
||||||
//PK can be null if the entity doesn't exist
|
//PK can be null if the entity doesn't exist
|
||||||
if (pk != null) {
|
if (pk != null) {
|
||||||
event.getSession().getPersistenceContext().cacheNaturalIdResolution(
|
event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
|
||||||
event.getEntityPersister(),
|
event.getEntityPersister(),
|
||||||
pk,
|
pk,
|
||||||
event.getOrderedNaturalIdValues(),
|
event.getOrderedNaturalIdValues()
|
||||||
CachedNaturalIdValueSource.LOAD
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@ import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -81,6 +80,7 @@ import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.TypeHelper;
|
import org.hibernate.TypeHelper;
|
||||||
import org.hibernate.UnknownProfileException;
|
import org.hibernate.UnknownProfileException;
|
||||||
import org.hibernate.UnresolvableObjectException;
|
import org.hibernate.UnresolvableObjectException;
|
||||||
|
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
import org.hibernate.criterion.NaturalIdentifier;
|
import org.hibernate.criterion.NaturalIdentifier;
|
||||||
import org.hibernate.engine.internal.StatefulPersistenceContext;
|
import org.hibernate.engine.internal.StatefulPersistenceContext;
|
||||||
|
@ -90,6 +90,7 @@ import org.hibernate.engine.query.spi.HQLQueryPlan;
|
||||||
import org.hibernate.engine.query.spi.NativeSQLQueryPlan;
|
import org.hibernate.engine.query.spi.NativeSQLQueryPlan;
|
||||||
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
||||||
import org.hibernate.engine.spi.ActionQueue;
|
import org.hibernate.engine.spi.ActionQueue;
|
||||||
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
import org.hibernate.engine.spi.CollectionEntry;
|
import org.hibernate.engine.spi.CollectionEntry;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
|
@ -2406,6 +2407,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
private abstract class BaseNaturalIdLoadAccessImpl {
|
private abstract class BaseNaturalIdLoadAccessImpl {
|
||||||
private final EntityPersister entityPersister;
|
private final EntityPersister entityPersister;
|
||||||
private LockOptions lockOptions;
|
private LockOptions lockOptions;
|
||||||
|
private boolean synchronizationEnabled = true;
|
||||||
|
|
||||||
private BaseNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
|
private BaseNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
|
||||||
this.entityPersister = entityPersister;
|
this.entityPersister = entityPersister;
|
||||||
|
@ -2430,18 +2432,59 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void synchronizationEnabled(boolean synchronizationEnabled) {
|
||||||
|
this.synchronizationEnabled = synchronizationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
protected final Serializable resolveNaturalId(Map<String, Object> naturalIdParameters) {
|
protected final Serializable resolveNaturalId(Map<String, Object> naturalIdParameters) {
|
||||||
final Set<Serializable> querySpaces = new LinkedHashSet<Serializable>();
|
performAnyNeededCrossReferenceSynchronizations();
|
||||||
for ( final Serializable querySpace : entityPersister.getQuerySpaces() ) {
|
|
||||||
querySpaces.add( querySpace );
|
|
||||||
}
|
|
||||||
|
|
||||||
autoFlushIfRequired( querySpaces );
|
|
||||||
|
|
||||||
final ResolveNaturalIdEvent event =
|
final ResolveNaturalIdEvent event =
|
||||||
new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, SessionImpl.this );
|
new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, SessionImpl.this );
|
||||||
fireResolveNaturalId( event );
|
fireResolveNaturalId( event );
|
||||||
return event.getEntityId();
|
|
||||||
|
if ( event.getEntityId() == PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return event.getEntityId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void performAnyNeededCrossReferenceSynchronizations() {
|
||||||
|
if ( ! synchronizationEnabled ) {
|
||||||
|
// synchronization (this process) was disabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! isTransactionInProgress() ) {
|
||||||
|
// not in a transaction so skip synchronization
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( entityPersister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
||||||
|
// only mutable natural-ids need this processing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( Serializable pk : getPersistenceContext().getNaturalIdHelper().getCachedPkResolutions( entityPersister ) ) {
|
||||||
|
final EntityKey entityKey = generateEntityKey( pk, entityPersister );
|
||||||
|
final Object entity = getPersistenceContext().getEntity( entityKey );
|
||||||
|
final EntityEntry entry = getPersistenceContext().getEntry( entity );
|
||||||
|
|
||||||
|
if ( !entry.requiresDirtyCheck( entity ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGED is the only status we care about here...
|
||||||
|
if ( entry.getStatus() != Status.MANAGED ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPersistenceContext().getNaturalIdHelper().handleSynchronization(
|
||||||
|
entityPersister,
|
||||||
|
pk,
|
||||||
|
entity
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final IdentifierLoadAccess getIdentifierLoadAccess() {
|
protected final IdentifierLoadAccess getIdentifierLoadAccess() {
|
||||||
|
@ -2451,6 +2494,10 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
}
|
}
|
||||||
return identifierLoadAccess;
|
return identifierLoadAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected EntityPersister entityPersister() {
|
||||||
|
return entityPersister;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NaturalIdLoadAccessImpl extends BaseNaturalIdLoadAccessImpl implements NaturalIdLoadAccess {
|
private class NaturalIdLoadAccessImpl extends BaseNaturalIdLoadAccessImpl implements NaturalIdLoadAccess {
|
||||||
|
@ -2479,6 +2526,12 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NaturalIdLoadAccessImpl setSynchronizationEnabled(boolean synchronizationEnabled) {
|
||||||
|
super.synchronizationEnabled( synchronizationEnabled );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object getReference() {
|
public final Object getReference() {
|
||||||
final Serializable entityId = resolveNaturalId( this.naturalIdParameters );
|
final Serializable entityId = resolveNaturalId( this.naturalIdParameters );
|
||||||
|
@ -2531,6 +2584,12 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
return Collections.singletonMap( naturalIdAttributeName, naturalIdValue );
|
return Collections.singletonMap( naturalIdAttributeName, naturalIdValue );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleNaturalIdLoadAccessImpl setSynchronizationEnabled(boolean synchronizationEnabled) {
|
||||||
|
super.synchronizationEnabled( synchronizationEnabled );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getReference(Object naturalIdValue) {
|
public Object getReference(Object naturalIdValue) {
|
||||||
final Serializable entityId = resolveNaturalId( getNaturalIdParameters( naturalIdValue ) );
|
final Serializable entityId = resolveNaturalId( getNaturalIdParameters( naturalIdValue ) );
|
||||||
|
|
|
@ -717,7 +717,7 @@ public class EntityMetamodel implements Serializable {
|
||||||
return naturalIdPropertyNumbers!=null;
|
return naturalIdPropertyNumbers!=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNatrualIdentifierCached() {
|
public boolean isNaturalIdentifierCached() {
|
||||||
return hasNaturalIdentifier() && hasCacheableNaturalId;
|
return hasNaturalIdentifier() && hasCacheableNaturalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.hibernate.Criteria;
|
import org.hibernate.Criteria;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
|
@ -40,6 +41,7 @@ import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,15 +104,7 @@ public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
|
||||||
tx.commit();
|
tx.commit();
|
||||||
s.close();
|
s.close();
|
||||||
|
|
||||||
//Clear naturalId cache that was populated when putting the data
|
|
||||||
s.getSessionFactory().getCache().evictNaturalIdRegions();
|
s.getSessionFactory().getCache().evictNaturalIdRegions();
|
||||||
|
|
||||||
s = openSession();
|
|
||||||
tx = s.beginTransaction();
|
|
||||||
Criteria criteria = s.createCriteria( NaturalIdOnManyToOne.class );
|
|
||||||
criteria.add( Restrictions.naturalId().set( "citizen", c1 ) );
|
|
||||||
criteria.setCacheable( true );
|
|
||||||
|
|
||||||
Statistics stats = sessionFactory().getStatistics();
|
Statistics stats = sessionFactory().getStatistics();
|
||||||
stats.setStatisticsEnabled( true );
|
stats.setStatisticsEnabled( true );
|
||||||
stats.clear();
|
stats.clear();
|
||||||
|
@ -119,6 +113,12 @@ public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
|
||||||
assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() );
|
assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() );
|
||||||
assertEquals( "NaturalId cache misses should be zero", 0, stats.getNaturalIdCacheMissCount() );
|
assertEquals( "NaturalId cache misses should be zero", 0, stats.getNaturalIdCacheMissCount() );
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
tx = s.beginTransaction();
|
||||||
|
Criteria criteria = s.createCriteria( NaturalIdOnManyToOne.class );
|
||||||
|
criteria.add( Restrictions.naturalId().set( "citizen", c1 ) );
|
||||||
|
criteria.setCacheable( true );
|
||||||
|
|
||||||
// first query
|
// first query
|
||||||
List results = criteria.list();
|
List results = criteria.list();
|
||||||
assertEquals( 1, results.size() );
|
assertEquals( 1, results.size() );
|
||||||
|
|
|
@ -91,10 +91,8 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
|
||||||
Statistics stats = sessionFactory().getStatistics();
|
Statistics stats = sessionFactory().getStatistics();
|
||||||
stats.setStatisticsEnabled( true );
|
stats.setStatisticsEnabled( true );
|
||||||
stats.clear();
|
stats.clear();
|
||||||
assertEquals(
|
assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
|
||||||
"Cache hits should be empty", 0, stats
|
assertEquals( "Cache puts should be empty", 0, stats.getNaturalIdCachePutCount() );
|
||||||
.getNaturalIdCacheHitCount()
|
|
||||||
);
|
|
||||||
|
|
||||||
// first query
|
// first query
|
||||||
List results = criteria.list();
|
List results = criteria.list();
|
||||||
|
|
|
@ -134,7 +134,7 @@ public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
|
||||||
assertNull( building );
|
assertNull( building );
|
||||||
assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
|
assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
|
||||||
assertEquals( "Cache misses should be two after second query", 2, stats.getNaturalIdCacheMissCount() );
|
assertEquals( "Cache misses should be two after second query", 2, stats.getNaturalIdCacheMissCount() );
|
||||||
assertEquals( "Cache put should be one after second query", 1, stats.getNaturalIdCachePutCount() );
|
assertEquals( "Cache put should be one after second query", 2, stats.getNaturalIdCachePutCount() );
|
||||||
assertEquals( "Query count should be two after second query", 2, stats.getNaturalIdQueryExecutionCount() );
|
assertEquals( "Query count should be two after second query", 2, stats.getNaturalIdQueryExecutionCount() );
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
|
@ -153,7 +153,7 @@ public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
|
||||||
assertNull( building );
|
assertNull( building );
|
||||||
assertEquals( "Cache hits should be one after third query", 1, stats.getNaturalIdCacheHitCount() );
|
assertEquals( "Cache hits should be one after third query", 1, stats.getNaturalIdCacheHitCount() );
|
||||||
assertEquals( "Cache misses should be one after third query", 3, stats.getNaturalIdCacheMissCount() );
|
assertEquals( "Cache misses should be one after third query", 3, stats.getNaturalIdCacheMissCount() );
|
||||||
assertEquals( "Cache put should be one after third query", 1, stats.getNaturalIdCachePutCount() );
|
assertEquals( "Cache put should be one after third query", 2, stats.getNaturalIdCachePutCount() );
|
||||||
assertEquals( "Query count should be one after third query", 3, stats.getNaturalIdQueryExecutionCount() );
|
assertEquals( "Query count should be one after third query", 3, stats.getNaturalIdQueryExecutionCount() );
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
|
|
|
@ -69,8 +69,8 @@ public class MutableNaturalIdTest extends BaseCoreFunctionalTestCase {
|
||||||
s.beginTransaction();
|
s.beginTransaction();
|
||||||
u = (User) s.byId( User.class ).getReference( u.getId() );
|
u = (User) s.byId( User.class ).getReference( u.getId() );
|
||||||
u.setOrg( "ceylon" );
|
u.setOrg( "ceylon" );
|
||||||
s.flush();
|
|
||||||
User oldNaturalId = (User) s.byNaturalId( User.class ).using( "name", "gavin" ).using( "org", "hb" ).load();
|
User oldNaturalId = (User) s.byNaturalId( User.class ).using( "name", "gavin" ).using( "org", "hb" ).load();
|
||||||
|
assertNull( oldNaturalId );
|
||||||
assertNotSame( u, oldNaturalId );
|
assertNotSame( u, oldNaturalId );
|
||||||
s.getTransaction().commit();
|
s.getTransaction().commit();
|
||||||
s.close();
|
s.close();
|
||||||
|
|
Loading…
Reference in New Issue