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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() );
|
||||
return entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
|
||||
}
|
||||
|
||||
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() );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
||||
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
|
||||
@Override
|
||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
public void cacheNaturalIdCrossReferenceFromLoad(
|
||||
EntityPersister persister,
|
||||
Serializable id,
|
||||
Object[] naturalIdValues) {
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageLocalNaturalIdCrossReference(
|
||||
EntityPersister persister,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
Object[] previousState,
|
||||
CachedNaturalIdValueSource source) {
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
persister = locateProperPersister( persister );
|
||||
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
|
||||
|
||||
// cache
|
||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
||||
persister,
|
||||
entityEntry.getId(),
|
||||
naturalIdValues,
|
||||
CachedNaturalIdValueSource.INSERT
|
||||
);
|
||||
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
public void manageSharedNaturalIdCrossReference(
|
||||
EntityPersister persister,
|
||||
final Serializable id,
|
||||
Object[] state,
|
||||
Object[] previousState,
|
||||
CachedNaturalIdValueSource source) {
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !persister.hasNaturalIdCache() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
persister = locateProperPersister( persister );
|
||||
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
|
||||
|
||||
managedSharedCacheEntries( persister, id, naturalIdValues, source );
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
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 );
|
||||
|
||||
// re-cache
|
||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
||||
final Object[] localNaturalIdValues = naturalIdXrefDelegate.removeNaturalIdCrossReference(
|
||||
persister,
|
||||
entityEntry.getId(),
|
||||
naturalIdValues,
|
||||
CachedNaturalIdValueSource.UPDATE
|
||||
id,
|
||||
naturalIdValues
|
||||
);
|
||||
|
||||
return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
public void removeSharedNaturalIdCrossReference(EntityPersister persister, Serializable id, Object[] naturalIdValues) {
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
final Object[] naturalIdValues = getNaturalIdValues( deletedState, persister );
|
||||
if ( ! persister.hasNaturalIdCache() ) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// evict from cache
|
||||
naturalIdXrefDelegate.evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,14 +124,32 @@ 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();
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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() {};
|
||||
|
||||
/**
|
||||
* Callback used to signal that entity state has been inserted.
|
||||
* Given an array of "full entity state", extract the portions that represent the natural id
|
||||
*
|
||||
* @param entityEntry The entry of the inserted entity
|
||||
* @param state The new state
|
||||
* @param state The attribute state array
|
||||
* @param persister The persister representing the entity type.
|
||||
*
|
||||
* @return The extracted natural id values
|
||||
*/
|
||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state);
|
||||
public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister);
|
||||
|
||||
/**
|
||||
* Callback used to signal that entity state has been deleted.
|
||||
* Given an entity instance, extract the values that represent the natural id
|
||||
*
|
||||
* @param entityEntry The entry of the inserted entity
|
||||
* @param deletedState The state of the entity at the time of deletion
|
||||
* @param entity The entity instance
|
||||
* @param persister The persister representing the entity type.
|
||||
*
|
||||
* @return The extracted natural id values
|
||||
*/
|
||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState);
|
||||
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);
|
||||
|
||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalId);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
public void cacheNaturalIdResolution(EntityPersister persister, Serializable pk, Object[] naturalId, CachedNaturalIdValueSource valueSource);
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to the natural-id helper for this persistence context
|
||||
*
|
||||
* @return This persistence context's natural-id helper
|
||||
*/
|
||||
public NaturalIdHelper getNaturalIdHelper();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,19 +2432,60 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
|||
return this;
|
||||
}
|
||||
|
||||
protected final Serializable resolveNaturalId(Map<String, Object> naturalIdParameters) {
|
||||
final Set<Serializable> querySpaces = new LinkedHashSet<Serializable>();
|
||||
for ( final Serializable querySpace : entityPersister.getQuerySpaces() ) {
|
||||
querySpaces.add( querySpace );
|
||||
protected void synchronizationEnabled(boolean synchronizationEnabled) {
|
||||
this.synchronizationEnabled = synchronizationEnabled;
|
||||
}
|
||||
|
||||
autoFlushIfRequired( querySpaces );
|
||||
protected final Serializable resolveNaturalId(Map<String, Object> naturalIdParameters) {
|
||||
performAnyNeededCrossReferenceSynchronizations();
|
||||
|
||||
final ResolveNaturalIdEvent event =
|
||||
new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, SessionImpl.this );
|
||||
fireResolveNaturalId( event );
|
||||
|
||||
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() {
|
||||
final IdentifierLoadAccessImpl identifierLoadAccess = new IdentifierLoadAccessImpl( entityPersister );
|
||||
|
@ -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 ) );
|
||||
|
|
|
@ -717,7 +717,7 @@ public class EntityMetamodel implements Serializable {
|
|||
return naturalIdPropertyNumbers!=null;
|
||||
}
|
||||
|
||||
public boolean isNatrualIdentifierCached() {
|
||||
public boolean isNaturalIdentifierCached() {
|
||||
return hasNaturalIdentifier() && hasCacheableNaturalId;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue