HHH-7206 - Manage natural-id synchronization without flushing

This commit is contained in:
Steve Ebersole 2012-04-03 15:51:36 -05:00
parent 52d5d374e5
commit 3800a0e695
20 changed files with 884 additions and 314 deletions

View File

@ -51,6 +51,24 @@ public interface NaturalIdLoadAccess {
*/
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
* method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.

View File

@ -42,6 +42,19 @@ public interface SimpleNaturalIdLoadAccess {
*/
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
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.

View File

@ -30,6 +30,7 @@ import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.internal.NonNullableTransientDependencies;
import org.hibernate.engine.internal.Nullability;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionImplementor;
@ -70,6 +71,8 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
this.isVersionIncrementDisabled = isVersionIncrementDisabled;
this.isExecuted = false;
this.areTransientReferencesNullified = false;
handleNaturalIdPreSaveNotifications();
}
/**
@ -172,4 +175,32 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
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
);
}
}

View File

@ -29,6 +29,7 @@ import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.cache.spi.CacheKey;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
@ -46,6 +47,7 @@ public final class EntityDeleteAction extends EntityAction {
private final Object[] state;
private SoftLock lock;
private Object[] naturalIdValues;
public EntityDeleteAction(
final Serializable id,
@ -59,6 +61,13 @@ public final class EntityDeleteAction extends EntityAction {
this.version = version;
this.isCascadeDeleteEnabled = isCascadeDeleteEnabled;
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
@ -109,6 +118,8 @@ public final class EntityDeleteAction extends EntityAction {
persister.getCacheAccessStrategy().remove( ck );
}
persistenceContext.getNaturalIdHelper().removeSharedNaturalIdCrossReference( persister, id, naturalIdValues );
postDelete();
if ( getSession().getFactory().getStatistics().isStatisticsEnabled() && !veto ) {

View File

@ -89,7 +89,7 @@ public final class EntityInsertAction extends AbstractEntityInsertAction {
EntityEntry entry = session.getPersistenceContext().getEntry( instance );
if ( entry == null ) {
throw new AssertionFailure( "possible nonthreadsafe access to session" );
throw new AssertionFailure( "possible non-threadsafe access to session" );
}
entry.postInsert( getState() );
@ -125,9 +125,10 @@ public final class EntityInsertAction extends AbstractEntityInsertAction {
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().secondLevelCachePut( getPersister().getCacheAccessStrategy().getRegion().getName() );
}
}
handleNaturalIdPostSaveNotifications();
postInsert();
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {

View File

@ -32,6 +32,7 @@ import org.hibernate.cache.spi.CacheKey;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
@ -76,6 +77,14 @@ public final class EntityUpdateAction extends EntityAction {
this.dirtyFields = dirtyProperties;
this.hasDirtyCollection = hasDirtyCollection;
this.rowId = rowId;
session.getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference(
persister,
id,
state,
previousState,
CachedNaturalIdValueSource.UPDATE
);
}
@Override
@ -174,6 +183,14 @@ public final class EntityUpdateAction extends EntityAction {
}
}
session.getPersistenceContext().getNaturalIdHelper().manageSharedNaturalIdCrossReference(
persister,
id,
state,
previousState,
CachedNaturalIdValueSource.UPDATE
);
postUpdate();
if ( factory.getStatistics().isStatisticsEnabled() && !veto ) {

View File

@ -24,28 +24,30 @@
package org.hibernate.engine.internal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.pretty.MessageHelper;
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.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
/**
* 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
* actions related to the shared caching of the entity's natural id.
* identifiers and natural ids of entities associated with the PersistenceContext.
* <p/>
* Most operations resolve the proper {@link NaturalIdResolutionCache} to use based on the persister and
* simply delegate calls there.
*
* @author Steve Ebersole
*/
@ -59,132 +61,49 @@ public class NaturalIdXrefDelegate {
this.persistenceContext = persistenceContext;
}
/**
* Access to the session (via the PersistenceContext) to which this delegate ultimately belongs.
*
* @return The session
*/
protected SessionImplementor session() {
return persistenceContext.getSession();
}
public void cacheNaturalIdResolution(
EntityPersister persister,
final Serializable pk,
Object[] naturalIdValues,
CachedNaturalIdValueSource valueSource) {
persister = locatePersisterForKey( persister );
/**
* Creates needed cross-reference entries between the given primary (pk) and natural (naturalIdValues) key values
* for the given persister. Returns an indication of whether entries were actually made. If those values already
* existed as an entry, {@code false} would be returned here.
*
* @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 );
Object[] previousNaturalIdValues = valueSource == CachedNaturalIdValueSource.UPDATE
? persistenceContext.getNaturalIdSnapshot( pk, persister )
: null;
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
}
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;
}
}
}
return entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
}
protected EntityPersister locatePersisterForKey(EntityPersister persister) {
return persistenceContext.getSession().getFactory().getEntityPersister( persister.getRootEntityName() );
}
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." );
}
}
public void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] deletedNaturalIdValues) {
/**
* Handle removing cross reference entries for the given natural-id/pk combo
*
* @param persister The persister representing the entity type.
* @param pk The primary key value
* @param naturalIdValues The natural id value(s)
*
* @return The cached values, if any. May be different from incoming values.
*/
public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
persister = locatePersisterForKey( persister );
validateNaturalId( persister, deletedNaturalIdValues );
validateNaturalId( persister, naturalIdValues );
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
Object[] sessionCachedNaturalIdValues = null;
@ -200,17 +119,72 @@ public class NaturalIdXrefDelegate {
if ( persister.hasNaturalIdCache() ) {
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
.getNaturalIdCacheAccessStrategy();
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( deletedNaturalIdValues, persister, session() );
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session() );
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
if ( sessionCachedNaturalIdValues != null
&& !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
&& !Arrays.equals( sessionCachedNaturalIdValues, naturalIdValues ) ) {
final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session() );
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) {
persister = locatePersisterForKey( persister );
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
@ -226,6 +200,18 @@ public class NaturalIdXrefDelegate {
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) {
persister = locatePersisterForKey( persister );
validateNaturalId( persister, naturalIdValues );
@ -249,6 +235,11 @@ public class NaturalIdXrefDelegate {
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
@ -296,7 +287,68 @@ public class NaturalIdXrefDelegate {
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 final EntityPersister persister;
private final Object[] values;
@ -347,10 +399,10 @@ public class NaturalIdXrefDelegate {
}
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
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
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 final EntityPersister persister;
private final Type[] naturalIdTypes;
@ -368,6 +423,8 @@ public class NaturalIdXrefDelegate {
private Map<Serializable, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, CachedNaturalId>();
private Map<CachedNaturalId, Serializable> naturalIdToPkMap = new ConcurrentHashMap<CachedNaturalId, Serializable>();
private List<CachedNaturalId> invalidNaturalIdList;
private NaturalIdResolutionCache(EntityPersister persister) {
this.persister = persister;
@ -383,10 +440,20 @@ public class NaturalIdXrefDelegate {
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) {
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
if ( initial != null ) {
if ( areSame( naturalIdValues, initial.getValues() ) ) {
if ( initial.isSame( naturalIdValues ) ) {
return false;
}
naturalIdToPkMap.remove( initial );
@ -399,14 +466,22 @@ public class NaturalIdXrefDelegate {
return true;
}
private boolean areSame(Object[] naturalIdValues, Object[] values) {
// lengths have already been verified at this point
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
if ( ! naturalIdTypes[i].isEqual( naturalIdValues[i], values[i] ) ) {
return false;
}
public void stashInvalidNaturalIdReference(Object[] invalidNaturalIdValues) {
if ( invalidNaturalIdList == null ) {
invalidNaturalIdList = new ArrayList<CachedNaturalId>();
}
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;
}
}
}

View File

@ -29,6 +29,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@ -47,6 +48,10 @@ import org.hibernate.MappingException;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.PersistentObjectException;
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.engine.loading.internal.LoadContexts;
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.EntityUniqueKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
@ -313,8 +320,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
return null;
}
persister = locateProperPersister( persister );
// 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 ) {
return cachedValue;
}
@ -323,7 +332,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
cacheNaturalIdResolution( persister, id, dbValue, CachedNaturalIdValueSource.LOAD );
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
persister,
id,
dbValue
);
return dbValue;
}
else {
@ -339,11 +352,19 @@ public class StatefulPersistenceContext implements PersistenceContext {
for ( int i = 0; i < props.length; i++ ) {
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
}
cacheNaturalIdResolution( persister, id, naturalIdSnapshotSubSet, CachedNaturalIdValueSource.LOAD );
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
persister,
id,
naturalIdSnapshotSubSet
);
return naturalIdSnapshotSubSet;
}
}
private EntityPersister locateProperPersister(EntityPersister persister) {
return session.getFactory().getEntityPersister( persister.getRootEntityName() );
}
/**
* Retrieve the cached database snapshot for the requested entity key.
* <p/>
@ -1091,7 +1112,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
@Override
public void setFlushing(boolean flushing) {
final boolean afterFlush = this.flushing && ! flushing;
this.flushing = flushing;
if ( afterFlush ) {
getNaturalIdHelper().cleanupFromSynchronizations();
}
}
/**
@ -1699,58 +1724,293 @@ public class StatefulPersistenceContext implements PersistenceContext {
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
private final NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
@Override
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state) {
final EntityPersister persister = entityEntry.getPersister();
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
@Override
public void cacheNaturalIdCrossReferenceFromLoad(
EntityPersister persister,
Serializable id,
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
naturalIdXrefDelegate.cacheNaturalIdResolution(
persister,
entityEntry.getId(),
naturalIdValues,
CachedNaturalIdValueSource.INSERT
);
}
persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
@Override
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state) {
final EntityPersister persister = entityEntry.getPersister();
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
// cache
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues );
}
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
naturalIdXrefDelegate.cacheNaturalIdResolution(
persister,
entityEntry.getId(),
naturalIdValues,
CachedNaturalIdValueSource.UPDATE
);
}
if ( !persister.hasNaturalIdCache() ) {
// nothing to do
return;
}
@Override
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState) {
final EntityPersister persister = entityEntry.getPersister();
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
managedSharedCacheEntries( persister, id, naturalIdValues, source );
}
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
naturalIdXrefDelegate.evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
final SessionFactoryImplementor factory = session.getFactory();
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) {
@ -1763,24 +2023,4 @@ public class StatefulPersistenceContext implements PersistenceContext {
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 );
}
}

View File

@ -124,23 +124,41 @@ public final class TwoPhaseLoad {
final SessionImplementor session,
final PreLoadEvent preLoadEvent,
final PostLoadEvent postLoadEvent) throws HibernateException {
//TODO: Should this be an InitializeEntityEventListener??? (watch out for performance!)
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 ) {
throw new AssertionFailure( "possible non-threadsafe access to the session" );
}
final PersistenceContext persistenceContext = session.getPersistenceContext();
EntityPersister persister = entityEntry.getPersister();
Serializable id = entityEntry.getId();
Object[] hydratedState = entityEntry.getLoadedState();
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"Resolving associations for %s",
MessageHelper.infoString( persister, id, session.getFactory() )
);
"Resolving associations for %s",
MessageHelper.infoString( persister, id, session.getFactory() )
);
}
Type[] types = persister.getPropertyTypes();
@ -172,9 +190,9 @@ public final class TwoPhaseLoad {
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"Adding entity to second-level cache: %s",
MessageHelper.infoString( persister, id, session.getFactory() )
);
"Adding entity to second-level cache: %s",
MessageHelper.infoString( persister, id, session.getFactory() )
);
}
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;
if ( !persister.isMutable() ) {
isReallyReadOnly = true;
@ -252,7 +278,7 @@ public final class TwoPhaseLoad {
entity,
entityEntry.isLoadedWithLazyPropertiesUnfetched(),
session
);
);
if ( session.isEventSource() ) {
postLoadEvent.setEntity( entity ).setId( id ).setPersister( persister );
@ -277,7 +303,6 @@ public final class TwoPhaseLoad {
if ( factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().loadEntity( persister.getEntityName() );
}
}
private static boolean useMinimalPuts(SessionImplementor session, EntityEntry entityEntry) {

View File

@ -240,8 +240,6 @@ public final class EntityEntry implements Serializable {
.getFactory()
.getCustomEntityDirtinessStrategy()
.resetDirty( entity, getPersister(), (Session) persistenceContext.getSession() );
notifyLoadedStateUpdated( updatedState );
}
/**
@ -252,8 +250,6 @@ public final class EntityEntry implements Serializable {
previousStatus = status;
status = Status.GONE;
existsInDatabase = false;
notifyLoadedStateDeleted( deletedState );
}
/**
@ -262,8 +258,6 @@ public final class EntityEntry implements Serializable {
*/
public void postInsert(Object[] insertedState) {
existsInDatabase = true;
notifyLoadedStateInserted( insertedState );
}
public boolean isNullifiable(boolean earlyInsert, SessionImplementor session) {
@ -360,7 +354,13 @@ public final class EntityEntry implements Serializable {
}
setStatus( Status.MANAGED );
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;
}
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
* Session/PersistenceContext for increased performance.

View File

@ -24,6 +24,7 @@
package org.hibernate.engine.spi;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
@ -684,33 +685,157 @@ public interface PersistenceContext {
public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id);
/**
* Callback used to signal that loaded entity state has changed.
*
* @param entityEntry The entry of the entity that has changed.
* @param state The new state.
* Provides centralized access to natural-id-related functionality.
*/
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.
*
* @param entityEntry The entry of the inserted entity
* @param state The new state
* Access to the natural-id helper for this persistence context
*
* @return This persistence context's natural-id helper
*/
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state);
/**
* 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);
public NaturalIdHelper getNaturalIdHelper();
}

View File

@ -109,7 +109,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
final Object[] snapshot = loaded == null
? session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister )
: extractNaturalIdValues( loaded, naturalIdentifierPropertiesIndexes );
: session.getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( loaded, persister );
for ( int i=0; i<naturalIdentifierPropertiesIndexes.length; 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
* an update action, if necessary

View File

@ -37,7 +37,6 @@ import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
@ -445,18 +444,10 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i
);
if (entity != null && persister.hasNaturalIdentifier()) {
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties();
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(
event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
persister,
event.getEntityId(),
naturalId,
CachedNaturalIdValueSource.LOAD
source.getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues( entity, persister )
);
}

View File

@ -103,7 +103,7 @@ public class DefaultResolveNaturalIdEventListener
* @return The entity from the cache, or null.
*/
protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
return event.getSession().getPersistenceContext().findCachedNaturalIdResolution(
return event.getSession().getPersistenceContext().getNaturalIdHelper().findCachedNaturalIdResolution(
event.getEntityPersister(),
event.getOrderedNaturalIdValues()
);
@ -142,11 +142,10 @@ public class DefaultResolveNaturalIdEventListener
//PK can be null if the entity doesn't exist
if (pk != null) {
event.getSession().getPersistenceContext().cacheNaturalIdResolution(
event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
event.getEntityPersister(),
pk,
event.getOrderedNaturalIdValues(),
CachedNaturalIdValueSource.LOAD
event.getOrderedNaturalIdValues()
);
}

View File

@ -41,7 +41,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -81,6 +80,7 @@ import org.hibernate.TransientObjectException;
import org.hibernate.TypeHelper;
import org.hibernate.UnknownProfileException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.cache.spi.NaturalIdCacheKey;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.criterion.NaturalIdentifier;
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.sql.NativeSQLQuerySpecification;
import org.hibernate.engine.spi.ActionQueue;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
@ -2406,6 +2407,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
private abstract class BaseNaturalIdLoadAccessImpl {
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private boolean synchronizationEnabled = true;
private BaseNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
this.entityPersister = entityPersister;
@ -2430,18 +2432,59 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
return this;
}
protected void synchronizationEnabled(boolean synchronizationEnabled) {
this.synchronizationEnabled = synchronizationEnabled;
}
protected final Serializable resolveNaturalId(Map<String, Object> naturalIdParameters) {
final Set<Serializable> querySpaces = new LinkedHashSet<Serializable>();
for ( final Serializable querySpace : entityPersister.getQuerySpaces() ) {
querySpaces.add( querySpace );
}
autoFlushIfRequired( querySpaces );
performAnyNeededCrossReferenceSynchronizations();
final ResolveNaturalIdEvent event =
new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, SessionImpl.this );
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() {
@ -2451,6 +2494,10 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
}
return identifierLoadAccess;
}
protected EntityPersister entityPersister() {
return entityPersister;
}
}
private class NaturalIdLoadAccessImpl extends BaseNaturalIdLoadAccessImpl implements NaturalIdLoadAccess {
@ -2479,6 +2526,12 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
return this;
}
@Override
public NaturalIdLoadAccessImpl setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
public final Object getReference() {
final Serializable entityId = resolveNaturalId( this.naturalIdParameters );
@ -2531,6 +2584,12 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
return Collections.singletonMap( naturalIdAttributeName, naturalIdValue );
}
@Override
public SimpleNaturalIdLoadAccessImpl setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
public Object getReference(Object naturalIdValue) {
final Serializable entityId = resolveNaturalId( getNaturalIdParameters( naturalIdValue ) );

View File

@ -717,7 +717,7 @@ public class EntityMetamodel implements Serializable {
return naturalIdPropertyNumbers!=null;
}
public boolean isNatrualIdentifierCached() {
public boolean isNaturalIdentifierCached() {
return hasNaturalIdentifier() && hasCacheableNaturalId;
}

View File

@ -30,6 +30,7 @@ import org.junit.After;
import org.junit.Test;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
@ -40,6 +41,7 @@ import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
@ -102,15 +104,7 @@ public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
tx.commit();
s.close();
//Clear naturalId cache that was populated when putting the data
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();
stats.setStatisticsEnabled( true );
stats.clear();
@ -119,6 +113,12 @@ public class NaturalIdOnSingleManyToOneTest extends BaseCoreFunctionalTestCase {
assertEquals( "NaturalId Cache Puts", 0, stats.getNaturalIdCachePutCount() );
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
List results = criteria.list();
assertEquals( 1, results.size() );

View File

@ -91,10 +91,8 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true );
stats.clear();
assertEquals(
"Cache hits should be empty", 0, stats
.getNaturalIdCacheHitCount()
);
assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache puts should be empty", 0, stats.getNaturalIdCachePutCount() );
// first query
List results = criteria.list();

View File

@ -134,7 +134,7 @@ public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
assertNull( building );
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 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() );
// cleanup
@ -153,7 +153,7 @@ public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
assertNull( building );
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 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() );
// cleanup

View File

@ -69,8 +69,8 @@ public class MutableNaturalIdTest extends BaseCoreFunctionalTestCase {
s.beginTransaction();
u = (User) s.byId( User.class ).getReference( u.getId() );
u.setOrg( "ceylon" );
s.flush();
User oldNaturalId = (User) s.byNaturalId( User.class ).using( "name", "gavin" ).using( "org", "hb" ).load();
assertNull( oldNaturalId );
assertNotSame( u, oldNaturalId );
s.getTransaction().commit();
s.close();