More natural-id work
- preliminary work for natural-id caching support - re-worked the previous NaturalIdHelper, NaturalIdXRefDelegate, etc - minor fixes/improvements to previous commit
This commit is contained in:
parent
270fba830a
commit
70baa0b659
|
@ -14,7 +14,6 @@ import org.hibernate.engine.internal.Versioning;
|
||||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.engine.spi.Status;
|
import org.hibernate.engine.spi.Status;
|
||||||
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
||||||
|
@ -166,7 +165,7 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
// before save, we need to add a local (transactional) natural id cross-reference
|
// before save, we need to add a local (transactional) natural id cross-reference
|
||||||
final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
|
final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
|
||||||
if ( naturalIdMapping != null ) {
|
if ( naturalIdMapping != null ) {
|
||||||
getSession().getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
|
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||||
getId(),
|
getId(),
|
||||||
naturalIdMapping.extractNaturalIdValues( state, getSession() ),
|
naturalIdMapping.extractNaturalIdValues( state, getSession() ),
|
||||||
getPersister(),
|
getPersister(),
|
||||||
|
@ -188,10 +187,9 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
|
|
||||||
final Object naturalIdValues = naturalIdMapping.extractNaturalIdValues( state, getSession() );
|
final Object naturalIdValues = naturalIdMapping.extractNaturalIdValues( state, getSession() );
|
||||||
|
|
||||||
final PersistenceContext.NaturalIdHelper naturalIdHelper = getSession().getPersistenceContextInternal().getNaturalIdHelper();
|
|
||||||
if ( isEarlyInsert() ) {
|
if ( isEarlyInsert() ) {
|
||||||
// with early insert, we still need to add a local (transactional) natural id cross-reference
|
// with early insert, we still need to add a local (transactional) natural id cross-reference
|
||||||
naturalIdHelper.manageLocalResolution(
|
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||||
generatedId,
|
generatedId,
|
||||||
naturalIdValues,
|
naturalIdValues,
|
||||||
getPersister(),
|
getPersister(),
|
||||||
|
@ -199,7 +197,7 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// after save, we need to manage the shared cache entries
|
// after save, we need to manage the shared cache entries
|
||||||
naturalIdHelper.manageSharedResolution(
|
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageSharedResolution(
|
||||||
generatedId,
|
generatedId,
|
||||||
naturalIdValues,
|
naturalIdValues,
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -64,10 +64,10 @@ public class EntityDeleteAction extends EntityAction {
|
||||||
this.naturalIdMapping = persister.getNaturalIdMapping();
|
this.naturalIdMapping = persister.getNaturalIdMapping();
|
||||||
|
|
||||||
if ( naturalIdMapping != null ) {
|
if ( naturalIdMapping != null ) {
|
||||||
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdHelper().removeLocalResolution(
|
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdResolutions().removeLocalResolution(
|
||||||
getPersister(),
|
|
||||||
getId(),
|
getId(),
|
||||||
naturalIdMapping.extractNaturalIdValues( state, session )
|
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||||
|
getPersister()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ public class EntityDeleteAction extends EntityAction {
|
||||||
persister.getCacheAccessStrategy().remove( session, ck);
|
persister.getCacheAccessStrategy().remove( session, ck);
|
||||||
}
|
}
|
||||||
|
|
||||||
persistenceContext.getNaturalIdHelper().removeSharedResolution( persister, id, naturalIdValues );
|
persistenceContext.getNaturalIdResolutions().removeSharedResolution( persister, id, naturalIdValues );
|
||||||
|
|
||||||
postDelete();
|
postDelete();
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ public class EntityUpdateAction extends EntityAction {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, naturalIdMapping, id, previousState, session );
|
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, naturalIdMapping, id, previousState, session );
|
||||||
session.getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
|
session.getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||||
id,
|
id,
|
||||||
naturalIdMapping.extractNaturalIdValues( state, session ),
|
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||||
persister,
|
persister,
|
||||||
|
@ -224,7 +224,7 @@ public class EntityUpdateAction extends EntityAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( naturalIdMapping != null ) {
|
if ( naturalIdMapping != null ) {
|
||||||
session.getPersistenceContextInternal().getNaturalIdHelper().manageSharedResolution(
|
session.getPersistenceContextInternal().getNaturalIdResolutions().manageSharedResolution(
|
||||||
id,
|
id,
|
||||||
naturalIdMapping.extractNaturalIdValues( state, session ),
|
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||||
previousNaturalIdValues,
|
previousNaturalIdValues,
|
||||||
|
|
|
@ -408,7 +408,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
|
||||||
}
|
}
|
||||||
setStatus( Status.MANAGED );
|
setStatus( Status.MANAGED );
|
||||||
loadedState = getPersister().getPropertyValues( entity );
|
loadedState = getPersister().getPropertyValues( entity );
|
||||||
getPersistenceContext().getNaturalIdHelper().manageLocalResolution(
|
getPersistenceContext().getNaturalIdResolutions().manageLocalResolution(
|
||||||
id, loadedState, persister,
|
id, loadedState, persister,
|
||||||
CachedNaturalIdValueSource.LOAD
|
CachedNaturalIdValueSource.LOAD
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,772 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.engine.internal;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.hibernate.AssertionFailure;
|
||||||
|
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
||||||
|
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
|
||||||
|
import org.hibernate.cache.spi.access.SoftLock;
|
||||||
|
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||||
|
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||||
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
import org.hibernate.engine.spi.Resolution;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.event.spi.EventSource;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.stat.internal.StatsHelper;
|
||||||
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializable {
|
||||||
|
private static final Logger LOG = Logger.getLogger( NaturalIdResolutionsImpl.class );
|
||||||
|
|
||||||
|
private final StatefulPersistenceContext persistenceContext;
|
||||||
|
private final ConcurrentHashMap<EntityMappingType, EntityResolutions> resolutionsByEntity = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a NaturalIdXrefDelegate
|
||||||
|
*
|
||||||
|
* @param persistenceContext The persistence context that owns this delegate
|
||||||
|
*/
|
||||||
|
public NaturalIdResolutionsImpl(StatefulPersistenceContext persistenceContext) {
|
||||||
|
this.persistenceContext = persistenceContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to the session (via the PersistenceContext) to which this delegate ultimately belongs.
|
||||||
|
*
|
||||||
|
* @return The session
|
||||||
|
*/
|
||||||
|
protected SharedSessionContractImplementor session() {
|
||||||
|
return persistenceContext.getSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cacheResolution(Object pk, Object naturalIdValues, EntityMappingType entityDescriptor) {
|
||||||
|
validateNaturalId( entityDescriptor, naturalIdValues );
|
||||||
|
|
||||||
|
EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.get( entityDescriptor );
|
||||||
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
|
entityNaturalIdResolutionCache = new EntityResolutions( entityDescriptor, persistenceContext );
|
||||||
|
EntityResolutions previousInstance = resolutionsByEntity.putIfAbsent( entityDescriptor, entityNaturalIdResolutionCache );
|
||||||
|
if ( previousInstance != null ) {
|
||||||
|
entityNaturalIdResolutionCache = previousInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
||||||
|
validateNaturalId( persister, naturalId );
|
||||||
|
|
||||||
|
final EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.get( persister );
|
||||||
|
Object sessionCachedNaturalIdValues = null;
|
||||||
|
if ( entityNaturalIdResolutionCache != null ) {
|
||||||
|
final Resolution cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.remove( id );
|
||||||
|
if ( cachedNaturalId != null ) {
|
||||||
|
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( cachedNaturalId );
|
||||||
|
sessionCachedNaturalIdValues = cachedNaturalId.getNaturalIdValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( persister.hasNaturalIdCache() ) {
|
||||||
|
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalId, persister, session() );
|
||||||
|
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||||
|
|
||||||
|
if ( sessionCachedNaturalIdValues != null
|
||||||
|
&& ! naturalIdMapping.areEqual( sessionCachedNaturalIdValues, naturalId, session() ) ) {
|
||||||
|
final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( sessionCachedNaturalIdValues, persister, session() );
|
||||||
|
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionCachedNaturalIdValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void manageLocalResolution(
|
||||||
|
Object id,
|
||||||
|
Object naturalIdValue,
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
|
||||||
|
if ( naturalIdMapping == null ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache
|
||||||
|
cacheNaturalIdCrossReference( naturalIdValue, id, entityDescriptor.getRootEntityDescriptor() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeLocalResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
|
||||||
|
if ( naturalIdMapping == null ) {
|
||||||
|
// nothing to do
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
final Object localNaturalIdValues = removeNaturalIdCrossReference(
|
||||||
|
id,
|
||||||
|
naturalId,
|
||||||
|
persister
|
||||||
|
);
|
||||||
|
|
||||||
|
return localNaturalIdValues != null ? localNaturalIdValues : naturalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object removeNaturalIdCrossReference(Object id, Object naturalIdValue, EntityPersister persister) {
|
||||||
|
validateNaturalId( persister, naturalIdValue );
|
||||||
|
|
||||||
|
final EntityResolutions entityResolutions = this.resolutionsByEntity.get( persister );
|
||||||
|
Object sessionCachedNaturalIdValues = null;
|
||||||
|
if ( entityResolutions != null ) {
|
||||||
|
final Resolution cachedNaturalId = entityResolutions.pkToNaturalIdMap.remove( id );
|
||||||
|
if ( cachedNaturalId != null ) {
|
||||||
|
entityResolutions.naturalIdToPkMap.remove( cachedNaturalId );
|
||||||
|
sessionCachedNaturalIdValues = cachedNaturalId.getNaturalIdValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( persister.hasNaturalIdCache() ) {
|
||||||
|
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister
|
||||||
|
.getNaturalIdCacheAccessStrategy();
|
||||||
|
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValue, persister, session() );
|
||||||
|
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||||
|
|
||||||
|
if ( sessionCachedNaturalIdValues != null
|
||||||
|
&& ! Objects.deepEquals( sessionCachedNaturalIdValues, naturalIdValue ) ) {
|
||||||
|
final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( sessionCachedNaturalIdValues, persister, session() );
|
||||||
|
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionCachedNaturalIdValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cacheResolutionFromLoad(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
Object id,
|
||||||
|
Object naturalIdValues) {
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
if ( !persister.hasNaturalIdentifier() ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'justAddedLocally' is meant to handle the case where we would get double stats journaling
|
||||||
|
// 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.
|
||||||
|
final boolean justAddedLocally = cacheResolution( id, naturalIdValues, entityDescriptor );
|
||||||
|
|
||||||
|
if ( justAddedLocally && persister.hasNaturalIdCache() ) {
|
||||||
|
manageSharedResolution( persister, id, naturalIdValues, (Object) null, CachedNaturalIdValueSource.LOAD );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void manageSharedResolution(
|
||||||
|
final Object id,
|
||||||
|
Object naturalId,
|
||||||
|
Object previousNaturalId,
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
if ( naturalIdMapping == null ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( naturalIdMapping.getCacheAccess() == null ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityDescriptor = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
final Object naturalIdValues = naturalId;
|
||||||
|
|
||||||
|
// final Object previousNaturalIdValues = previousNaturalId == null ? null : extractNaturalIdValues( previousNaturalId, entityDescriptor );
|
||||||
|
final Object previousNaturalIdValues = previousNaturalId;
|
||||||
|
|
||||||
|
manageSharedResolution(
|
||||||
|
entityDescriptor.getEntityPersister(),
|
||||||
|
id,
|
||||||
|
naturalIdValues,
|
||||||
|
previousNaturalIdValues,
|
||||||
|
source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageSharedResolution(
|
||||||
|
EntityPersister persister,
|
||||||
|
final Object id,
|
||||||
|
Object naturalIdValues,
|
||||||
|
Object previousNaturalIdValues,
|
||||||
|
CachedNaturalIdValueSource source) {
|
||||||
|
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() );
|
||||||
|
|
||||||
|
final SessionFactoryImplementor factory = session().getFactory();
|
||||||
|
final StatisticsImplementor statistics = factory.getStatistics();
|
||||||
|
|
||||||
|
switch ( source ) {
|
||||||
|
case LOAD: {
|
||||||
|
if ( CacheHelper.fromSharedCache( session(), naturalIdCacheKey, naturalIdCacheAccessStrategy ) != null ) {
|
||||||
|
// prevent identical re-cachings
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
||||||
|
session(),
|
||||||
|
naturalIdCacheKey,
|
||||||
|
id,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( put && statistics.isStatisticsEnabled() ) {
|
||||||
|
statistics.naturalIdCachePut(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INSERT: {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.insert( session(), naturalIdCacheKey, id );
|
||||||
|
if ( put && statistics.isStatisticsEnabled() ) {
|
||||||
|
statistics.naturalIdCachePut(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
( (EventSource) session() ).getActionQueue().registerProcess(
|
||||||
|
new AfterTransactionCompletionProcess() {
|
||||||
|
@Override
|
||||||
|
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
|
||||||
|
if ( success ) {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.afterInsert( session, naturalIdCacheKey, id );
|
||||||
|
if ( put && statistics.isStatisticsEnabled() ) {
|
||||||
|
statistics.naturalIdCachePut(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UPDATE: {
|
||||||
|
final Object previousCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( previousNaturalIdValues, persister, session() );
|
||||||
|
if ( naturalIdCacheKey.equals( previousCacheKey ) ) {
|
||||||
|
// prevent identical re-caching, solves HHH-7309
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final SoftLock removalLock = naturalIdCacheAccessStrategy.lockItem( session(), previousCacheKey, null );
|
||||||
|
naturalIdCacheAccessStrategy.remove( session(), previousCacheKey);
|
||||||
|
|
||||||
|
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( session(), naturalIdCacheKey, null );
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.update( session(), naturalIdCacheKey, id );
|
||||||
|
if ( put && statistics.isStatisticsEnabled() ) {
|
||||||
|
statistics.naturalIdCachePut(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
( (EventSource) session() ).getActionQueue().registerProcess(
|
||||||
|
new AfterTransactionCompletionProcess() {
|
||||||
|
@Override
|
||||||
|
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
|
||||||
|
naturalIdCacheAccessStrategy.unlockItem( session(), previousCacheKey, removalLock );
|
||||||
|
if (success) {
|
||||||
|
final boolean put = naturalIdCacheAccessStrategy.afterUpdate(
|
||||||
|
session(),
|
||||||
|
naturalIdCacheKey,
|
||||||
|
id,
|
||||||
|
lock
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( put && statistics.isStatisticsEnabled() ) {
|
||||||
|
statistics.naturalIdCachePut(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
naturalIdCacheAccessStrategy.unlockItem( session(), naturalIdCacheKey, lock );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if ( LOG.isDebugEnabled() ) {
|
||||||
|
LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSharedResolution(EntityMappingType entityDescriptor, Object id, Object naturalIdValues) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
if ( naturalIdMapping == null || naturalIdMapping.getCacheAccess() == null ) {
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( 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 void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSynchronization(Object pk, Object entity, EntityMappingType entityDescriptor) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
if ( naturalIdMapping == null ) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
final Object naturalIdValuesFromCurrentObjectState = naturalIdMapping.extractNaturalIdValues( entity, session() );
|
||||||
|
final boolean changed = ! sameAsCached(
|
||||||
|
persister,
|
||||||
|
pk,
|
||||||
|
naturalIdValuesFromCurrentObjectState
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( changed ) {
|
||||||
|
final Object cachedNaturalIdValues = findCachedNaturalId( pk, persister );
|
||||||
|
cacheResolution( pk, naturalIdValuesFromCurrentObjectState, persister );
|
||||||
|
stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
|
||||||
|
|
||||||
|
removeSharedResolution(
|
||||||
|
persister,
|
||||||
|
pk,
|
||||||
|
cachedNaturalIdValues
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupFromSynchronizations() {
|
||||||
|
unStashInvalidNaturalIdReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleEviction(Object id, Object object, EntityMappingType entityDescriptor) {
|
||||||
|
removeResolution(
|
||||||
|
id,
|
||||||
|
findCachedNaturalId( id, entityDescriptor ),
|
||||||
|
entityDescriptor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean cacheNaturalIdCrossReference(Object naturalIdValue, Object pk, EntityMappingType entityDescriptor) {
|
||||||
|
validateNaturalId( entityDescriptor, naturalIdValue );
|
||||||
|
|
||||||
|
EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.get( entityDescriptor.getRootEntityDescriptor() );
|
||||||
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
|
entityNaturalIdResolutionCache = new EntityResolutions( entityDescriptor.getRootEntityDescriptor(), persistenceContext );
|
||||||
|
EntityResolutions previousInstance = resolutionsByEntity.putIfAbsent( entityDescriptor.getRootEntityDescriptor(), entityNaturalIdResolutionCache );
|
||||||
|
if ( previousInstance != null ) {
|
||||||
|
entityNaturalIdResolutionCache = previousInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entityNaturalIdResolutionCache.cache( pk, naturalIdValue );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, Object pk, Object naturalIdValues) {
|
||||||
|
final EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.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 entityDescriptor The entity type descriptor
|
||||||
|
* @param naturalIdValues The natural id values
|
||||||
|
*/
|
||||||
|
protected void validateNaturalId(EntityMappingType entityDescriptor, Object naturalIdValues) {
|
||||||
|
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||||
|
|
||||||
|
if ( naturalIdMapping == null ) {
|
||||||
|
throw new IllegalArgumentException( "Entity did not define a natural-id" );
|
||||||
|
}
|
||||||
|
|
||||||
|
naturalIdMapping.validateInternalForm( naturalIdValues, persistenceContext.getSession() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object findCachedNaturalId(Object id, EntityMappingType entityDescriptor) {
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
final EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.get( persister );
|
||||||
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Resolution cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( id );
|
||||||
|
if ( cachedNaturalId == null ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedNaturalId.getNaturalIdValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object findCachedNaturalIdResolution(Object naturalId, EntityMappingType entityDescriptor) {
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
validateNaturalId( persister, naturalId );
|
||||||
|
|
||||||
|
EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.get( persister );
|
||||||
|
|
||||||
|
Object pk;
|
||||||
|
final Resolution cachedNaturalId = new ResolutionImpl( persister, naturalId, persistenceContext );
|
||||||
|
if ( entityNaturalIdResolutionCache != null ) {
|
||||||
|
pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( cachedNaturalId );
|
||||||
|
|
||||||
|
// Found in session cache
|
||||||
|
if ( pk != null ) {
|
||||||
|
if ( LOG.isTraceEnabled() ) {
|
||||||
|
LOG.tracef(
|
||||||
|
"Resolved natural key (%s) -> primary key (%s) resolution in session cache for `%s`:",
|
||||||
|
naturalId,
|
||||||
|
pk,
|
||||||
|
entityDescriptor.getEntityName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we did not find a hit, see if we know about these natural ids as invalid...
|
||||||
|
if ( entityNaturalIdResolutionCache.containsInvalidNaturalIdReference( naturalId ) ) {
|
||||||
|
return NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session cache miss, see if second-level caching is enabled
|
||||||
|
if ( !persister.hasNaturalIdCache() ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try resolution from second-level cache
|
||||||
|
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||||
|
final SharedSessionContractImplementor session = session();
|
||||||
|
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalId, persister, session );
|
||||||
|
|
||||||
|
pk = CacheHelper.fromSharedCache( session, naturalIdCacheKey, naturalIdCacheAccessStrategy );
|
||||||
|
|
||||||
|
// Found in second-level cache, store in session cache
|
||||||
|
final SessionFactoryImplementor factory = session.getFactory();
|
||||||
|
final StatisticsImplementor statistics = factory.getStatistics();
|
||||||
|
final boolean statisticsEnabled = statistics.isStatisticsEnabled();
|
||||||
|
if ( pk != null ) {
|
||||||
|
if ( statisticsEnabled ) {
|
||||||
|
statistics.naturalIdCacheHit(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( LOG.isTraceEnabled() ) {
|
||||||
|
// protected to avoid Arrays.toString call unless needed
|
||||||
|
LOG.tracef(
|
||||||
|
"Found natural key [%s] -> primary key [%s] xref in second-level cache for %s",
|
||||||
|
naturalId,
|
||||||
|
pk,
|
||||||
|
persister.getRootEntityName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( entityNaturalIdResolutionCache == null ) {
|
||||||
|
entityNaturalIdResolutionCache = new EntityResolutions( persister, persistenceContext );
|
||||||
|
EntityResolutions existingCache = resolutionsByEntity.putIfAbsent( persister, entityNaturalIdResolutionCache );
|
||||||
|
if ( existingCache != null ) {
|
||||||
|
entityNaturalIdResolutionCache = existingCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, cachedNaturalId );
|
||||||
|
entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk );
|
||||||
|
}
|
||||||
|
else if ( statisticsEnabled ) {
|
||||||
|
statistics.naturalIdCacheMiss(
|
||||||
|
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
||||||
|
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<?> getCachedPkResolutions(EntityMappingType entityDescriptor) {
|
||||||
|
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
|
||||||
|
|
||||||
|
Collection<Object> pks = null;
|
||||||
|
|
||||||
|
final EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.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 EntityResolutions entityNaturalIdResolutionCache = resolutionsByEntity.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 ( EntityResolutions naturalIdResolutionCache : resolutionsByEntity.values() ) {
|
||||||
|
naturalIdResolutionCache.unStashInvalidNaturalIdReferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the resolution cache
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
resolutionsByEntity.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the persister-specific cross-reference cache.
|
||||||
|
*/
|
||||||
|
private static class EntityResolutions implements Serializable {
|
||||||
|
private final PersistenceContext persistenceContext;
|
||||||
|
|
||||||
|
private final EntityMappingType entityDescriptor;
|
||||||
|
|
||||||
|
private final Map<Object, Resolution> pkToNaturalIdMap = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Resolution, Object> naturalIdToPkMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private List<Resolution> invalidNaturalIdList;
|
||||||
|
|
||||||
|
private EntityResolutions(EntityMappingType entityDescriptor, PersistenceContext persistenceContext) {
|
||||||
|
this.entityDescriptor = entityDescriptor;
|
||||||
|
this.persistenceContext = persistenceContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return entityDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityPersister getPersister() {
|
||||||
|
return getEntityDescriptor().getEntityPersister();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sameAsCached(Object pk, Object naturalIdValues) {
|
||||||
|
if ( pk == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Resolution initial = pkToNaturalIdMap.get( pk );
|
||||||
|
if ( initial != null ) {
|
||||||
|
if ( initial.isSame( naturalIdValues ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean cache(Object pk, Object naturalIdValues) {
|
||||||
|
if ( pk == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Resolution initial = pkToNaturalIdMap.get( pk );
|
||||||
|
if ( initial != null ) {
|
||||||
|
if ( initial.isSame( naturalIdValues ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
naturalIdToPkMap.remove( initial );
|
||||||
|
}
|
||||||
|
|
||||||
|
final Resolution cachedNaturalId = new ResolutionImpl( getEntityDescriptor(), naturalIdValues, persistenceContext );
|
||||||
|
pkToNaturalIdMap.put( pk, cachedNaturalId );
|
||||||
|
naturalIdToPkMap.put( cachedNaturalId, pk );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stashInvalidNaturalIdReference(Object invalidNaturalIdValues) {
|
||||||
|
if ( invalidNaturalIdList == null ) {
|
||||||
|
invalidNaturalIdList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
invalidNaturalIdList.add( new ResolutionImpl( getEntityDescriptor(), invalidNaturalIdValues, persistenceContext ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsInvalidNaturalIdReference(Object naturalIdValues) {
|
||||||
|
return invalidNaturalIdList != null
|
||||||
|
&& invalidNaturalIdList.contains( new ResolutionImpl( getEntityDescriptor(), naturalIdValues, persistenceContext ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unStashInvalidNaturalIdReferences() {
|
||||||
|
if ( invalidNaturalIdList != null ) {
|
||||||
|
invalidNaturalIdList.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ResolutionImpl implements Resolution, Serializable {
|
||||||
|
private final PersistenceContext persistenceContext;
|
||||||
|
|
||||||
|
private final EntityMappingType entityDescriptor;
|
||||||
|
private final Object naturalIdValue;
|
||||||
|
|
||||||
|
private final int hashCode;
|
||||||
|
|
||||||
|
public ResolutionImpl(EntityMappingType entityDescriptor, Object naturalIdValue, PersistenceContext persistenceContext) {
|
||||||
|
this.entityDescriptor = entityDescriptor;
|
||||||
|
this.naturalIdValue = naturalIdValue;
|
||||||
|
this.persistenceContext = persistenceContext;
|
||||||
|
|
||||||
|
final int prime = 31;
|
||||||
|
int hashCodeCalculation = 1;
|
||||||
|
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.hashCode();
|
||||||
|
hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.getNaturalIdMapping().calculateHashCode( naturalIdValue, persistenceContext.getSession() );
|
||||||
|
|
||||||
|
this.hashCode = hashCodeCalculation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getNaturalIdValue() {
|
||||||
|
return naturalIdValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if ( this == obj ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( obj == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( getClass() != obj.getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ResolutionImpl other = (ResolutionImpl) obj;
|
||||||
|
return entityDescriptor.equals( other.entityDescriptor ) && isSame( other.naturalIdValue );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSame(Object otherValue) {
|
||||||
|
return entityDescriptor.getNaturalIdMapping().areEqual( naturalIdValue, otherValue, persistenceContext.getSession() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,485 +0,0 @@
|
||||||
/*
|
|
||||||
* Hibernate, Relational Persistence for Idiomatic Java
|
|
||||||
*
|
|
||||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
|
||||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
|
||||||
*/
|
|
||||||
package org.hibernate.engine.internal;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.hibernate.AssertionFailure;
|
|
||||||
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
|
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
|
||||||
import org.hibernate.stat.internal.StatsHelper;
|
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* <p/>
|
|
||||||
* Most operations resolve the proper {@link NaturalIdResolutionCache} to use based on the persister and
|
|
||||||
* simply delegate calls there.
|
|
||||||
*
|
|
||||||
* @author Steve Ebersole
|
|
||||||
*/
|
|
||||||
public class NaturalIdXrefDelegate {
|
|
||||||
private static final Logger LOG = Logger.getLogger( NaturalIdXrefDelegate.class );
|
|
||||||
|
|
||||||
private final StatefulPersistenceContext persistenceContext;
|
|
||||||
private final ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache> naturalIdResolutionCacheMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a NaturalIdXrefDelegate
|
|
||||||
*
|
|
||||||
* @param persistenceContext The persistence context that owns this delegate
|
|
||||||
*/
|
|
||||||
public NaturalIdXrefDelegate(StatefulPersistenceContext persistenceContext) {
|
|
||||||
this.persistenceContext = persistenceContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access to the session (via the PersistenceContext) to which this delegate ultimately belongs.
|
|
||||||
*
|
|
||||||
* @return The session
|
|
||||||
*/
|
|
||||||
protected SharedSessionContractImplementor session() {
|
|
||||||
return persistenceContext.getSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 cacheResolution(EntityPersister persister, Object pk, Object naturalIdValues) {
|
|
||||||
validateNaturalId( persister, naturalIdValues );
|
|
||||||
|
|
||||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
|
||||||
if ( entityNaturalIdResolutionCache == null ) {
|
|
||||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister, persistenceContext );
|
|
||||||
NaturalIdResolutionCache previousInstance = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache );
|
|
||||||
if ( previousInstance != null ) {
|
|
||||||
entityNaturalIdResolutionCache = previousInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entityNaturalIdResolutionCache.cache( pk, naturalIdValues );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 removeResolutions(EntityPersister persister, Object pk, Object naturalIdValues) {
|
|
||||||
persister = locatePersisterForKey( persister );
|
|
||||||
|
|
||||||
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
|
||||||
validateNaturalId( persister, naturalIdValues );
|
|
||||||
|
|
||||||
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
|
||||||
Object sessionCachedNaturalIdValues = null;
|
|
||||||
if ( entityNaturalIdResolutionCache != null ) {
|
|
||||||
final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.remove( pk );
|
|
||||||
if ( cachedNaturalId != null ) {
|
|
||||||
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( cachedNaturalId );
|
|
||||||
sessionCachedNaturalIdValues = cachedNaturalId.getNaturalId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( persister.hasNaturalIdCache() ) {
|
|
||||||
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
|
||||||
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() );
|
|
||||||
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
|
||||||
|
|
||||||
if ( sessionCachedNaturalIdValues != null
|
|
||||||
&& ! naturalIdMapping.areEqual( sessionCachedNaturalIdValues, naturalIdValues, session() ) ) {
|
|
||||||
final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( 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, Object pk, Object naturalIdValues) {
|
|
||||||
final 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 entityDescriptor The entity type descriptor
|
|
||||||
* @param naturalIdValues The natural id values
|
|
||||||
*/
|
|
||||||
protected void validateNaturalId(EntityPersister entityDescriptor, Object naturalIdValues) {
|
|
||||||
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
|
||||||
|
|
||||||
if ( naturalIdMapping == null ) {
|
|
||||||
throw new IllegalArgumentException( "Entity did not define a natural-id" );
|
|
||||||
}
|
|
||||||
|
|
||||||
naturalIdMapping.validateInternalForm( naturalIdValues, persistenceContext.getSession() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, Object pk) {
|
|
||||||
persister = locatePersisterForKey( persister );
|
|
||||||
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
|
||||||
if ( entityNaturalIdResolutionCache == null ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk );
|
|
||||||
if ( cachedNaturalId == null ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cachedNaturalId.getNaturalId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 Object findResolution(EntityPersister persister, Object naturalIdValues) {
|
|
||||||
persister = locatePersisterForKey( persister );
|
|
||||||
validateNaturalId( persister, naturalIdValues );
|
|
||||||
|
|
||||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
|
||||||
|
|
||||||
Object pk;
|
|
||||||
final CachedNaturalId cachedNaturalId = new CachedNaturalId( naturalIdValues, persister, persistenceContext );
|
|
||||||
if ( entityNaturalIdResolutionCache != null ) {
|
|
||||||
pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( cachedNaturalId );
|
|
||||||
|
|
||||||
// Found in session cache
|
|
||||||
if ( pk != null ) {
|
|
||||||
if ( LOG.isTraceEnabled() ) {
|
|
||||||
LOG.trace(
|
|
||||||
"Resolved natural key -> primary key resolution in session cache: " +
|
|
||||||
persister.getRootEntityName() + "#[" +
|
|
||||||
naturalIdValues + "]"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
if ( !persister.hasNaturalIdCache() ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try resolution from second-level cache
|
|
||||||
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
|
||||||
final SharedSessionContractImplementor session = session();
|
|
||||||
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister,
|
|
||||||
session
|
|
||||||
);
|
|
||||||
|
|
||||||
pk = CacheHelper.fromSharedCache( session, naturalIdCacheKey, naturalIdCacheAccessStrategy );
|
|
||||||
|
|
||||||
// Found in second-level cache, store in session cache
|
|
||||||
final SessionFactoryImplementor factory = session.getFactory();
|
|
||||||
final StatisticsImplementor statistics = factory.getStatistics();
|
|
||||||
final boolean statisticsEnabled = statistics.isStatisticsEnabled();
|
|
||||||
if ( pk != null ) {
|
|
||||||
if ( statisticsEnabled ) {
|
|
||||||
statistics.naturalIdCacheHit(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( LOG.isTraceEnabled() ) {
|
|
||||||
// protected to avoid Arrays.toString call unless needed
|
|
||||||
LOG.tracef(
|
|
||||||
"Found natural key [%s] -> primary key [%s] xref in second-level cache for %s",
|
|
||||||
naturalIdValues,
|
|
||||||
pk,
|
|
||||||
persister.getRootEntityName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( entityNaturalIdResolutionCache == null ) {
|
|
||||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister, persistenceContext );
|
|
||||||
NaturalIdResolutionCache existingCache = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache );
|
|
||||||
if ( existingCache != null ) {
|
|
||||||
entityNaturalIdResolutionCache = existingCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, cachedNaturalId );
|
|
||||||
entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk );
|
|
||||||
}
|
|
||||||
else if ( statisticsEnabled ) {
|
|
||||||
statistics.naturalIdCacheMiss(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Object> getCachedPkResolutions(EntityPersister persister) {
|
|
||||||
persister = locatePersisterForKey( persister );
|
|
||||||
|
|
||||||
Collection<Object> pks = null;
|
|
||||||
|
|
||||||
final 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 implements Serializable {
|
|
||||||
private final PersistenceContext persistenceContext;
|
|
||||||
private final EntityPersister persister;
|
|
||||||
private final Object naturalId;
|
|
||||||
private int hashCode;
|
|
||||||
|
|
||||||
public CachedNaturalId(Object naturalIdValue, EntityPersister persister, PersistenceContext persistenceContext) {
|
|
||||||
this.persistenceContext = persistenceContext;
|
|
||||||
|
|
||||||
this.persister = persister;
|
|
||||||
this.naturalId = naturalIdValue;
|
|
||||||
|
|
||||||
final int prime = 31;
|
|
||||||
int hashCodeCalculation = 1;
|
|
||||||
hashCodeCalculation = prime * hashCodeCalculation + persister.hashCode();
|
|
||||||
hashCodeCalculation = prime * hashCodeCalculation + persister.getNaturalIdMapping().calculateHashCode( naturalIdValue, persistenceContext.getSession());
|
|
||||||
|
|
||||||
this.hashCode = hashCodeCalculation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getNaturalId() {
|
|
||||||
return naturalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return this.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if ( this == obj ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ( obj == null ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( getClass() != obj.getClass() ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CachedNaturalId other = (CachedNaturalId) obj;
|
|
||||||
return persister.equals( other.persister ) && isSame( other.naturalId );
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSame(Object otherValue) {
|
|
||||||
return persister.getNaturalIdMapping().areEqual( naturalId, otherValue, persistenceContext.getSession() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the persister-specific cross-reference cache.
|
|
||||||
*/
|
|
||||||
private static class NaturalIdResolutionCache implements Serializable {
|
|
||||||
private final PersistenceContext persistenceContext;
|
|
||||||
|
|
||||||
private final EntityPersister persister;
|
|
||||||
|
|
||||||
private Map<Object, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<>();
|
|
||||||
private Map<CachedNaturalId, Object> naturalIdToPkMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private List<CachedNaturalId> invalidNaturalIdList;
|
|
||||||
|
|
||||||
private NaturalIdResolutionCache(EntityPersister persister, PersistenceContext persistenceContext) {
|
|
||||||
this.persister = persister;
|
|
||||||
this.persistenceContext = persistenceContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityPersister getPersister() {
|
|
||||||
return persister;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sameAsCached(Object pk, Object naturalIdValues) {
|
|
||||||
if ( pk == null ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
|
|
||||||
if ( initial != null ) {
|
|
||||||
if ( initial.isSame( naturalIdValues ) ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean cache(Object pk, Object naturalIdValues) {
|
|
||||||
if ( pk == null ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final CachedNaturalId initial = pkToNaturalIdMap.get( pk );
|
|
||||||
if ( initial != null ) {
|
|
||||||
if ( initial.isSame( naturalIdValues ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
naturalIdToPkMap.remove( initial );
|
|
||||||
}
|
|
||||||
|
|
||||||
final CachedNaturalId cachedNaturalId = new CachedNaturalId( naturalIdValues, persister, persistenceContext );
|
|
||||||
pkToNaturalIdMap.put( pk, cachedNaturalId );
|
|
||||||
naturalIdToPkMap.put( cachedNaturalId, pk );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stashInvalidNaturalIdReference(Object invalidNaturalIdValues) {
|
|
||||||
if ( invalidNaturalIdList == null ) {
|
|
||||||
invalidNaturalIdList = new ArrayList<>();
|
|
||||||
}
|
|
||||||
invalidNaturalIdList.add( new CachedNaturalId( invalidNaturalIdValues, persister, persistenceContext ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsInvalidNaturalIdReference(Object naturalIdValues) {
|
|
||||||
return invalidNaturalIdList != null
|
|
||||||
&& invalidNaturalIdList.contains( new CachedNaturalId( naturalIdValues, persister, persistenceContext ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unStashInvalidNaturalIdReferences() {
|
|
||||||
if ( invalidNaturalIdList != null ) {
|
|
||||||
invalidNaturalIdList.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the resolution cache
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
naturalIdResolutionCacheMap.clear();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -33,22 +32,19 @@ import org.hibernate.MappingException;
|
||||||
import org.hibernate.NonUniqueObjectException;
|
import org.hibernate.NonUniqueObjectException;
|
||||||
import org.hibernate.PersistentObjectException;
|
import org.hibernate.PersistentObjectException;
|
||||||
import org.hibernate.TransientObjectException;
|
import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||||
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
|
|
||||||
import org.hibernate.cache.spi.access.SoftLock;
|
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
import org.hibernate.engine.spi.AssociationKey;
|
import org.hibernate.engine.spi.AssociationKey;
|
||||||
import org.hibernate.engine.spi.BatchFetchQueue;
|
import org.hibernate.engine.spi.BatchFetchQueue;
|
||||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
|
||||||
import org.hibernate.engine.spi.CollectionEntry;
|
import org.hibernate.engine.spi.CollectionEntry;
|
||||||
import org.hibernate.engine.spi.CollectionKey;
|
import org.hibernate.engine.spi.CollectionKey;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||||
import org.hibernate.engine.spi.ManagedEntity;
|
import org.hibernate.engine.spi.ManagedEntity;
|
||||||
|
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||||
|
@ -56,12 +52,10 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.engine.spi.Status;
|
import org.hibernate.engine.spi.Status;
|
||||||
import org.hibernate.event.spi.EventSource;
|
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
||||||
import org.hibernate.internal.util.collections.IdentityMap;
|
import org.hibernate.internal.util.collections.IdentityMap;
|
||||||
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
|
||||||
import org.hibernate.metamodel.spi.MetamodelImplementor;
|
import org.hibernate.metamodel.spi.MetamodelImplementor;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
@ -69,8 +63,6 @@ import org.hibernate.pretty.MessageHelper;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
import org.hibernate.sql.results.spi.LoadContexts;
|
import org.hibernate.sql.results.spi.LoadContexts;
|
||||||
import org.hibernate.stat.internal.StatsHelper;
|
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
|
||||||
import org.hibernate.type.CollectionType;
|
import org.hibernate.type.CollectionType;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -268,7 +260,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
if ( loadContexts != null ) {
|
if ( loadContexts != null ) {
|
||||||
loadContexts.cleanup();
|
loadContexts.cleanup();
|
||||||
}
|
}
|
||||||
naturalIdXrefDelegate = null;
|
naturalIdResolutions = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -337,7 +329,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
persister = locateProperPersister( persister );
|
persister = locateProperPersister( persister );
|
||||||
|
|
||||||
// let's first see if it is part of the natural id cache...
|
// let's first see if it is part of the natural id cache...
|
||||||
final Object cachedValue = naturalIdHelper.findCachedNaturalId( persister, id );
|
final Object cachedValue = getNaturalIdResolutions().findCachedNaturalId( id, persister );
|
||||||
if ( cachedValue != null ) {
|
if ( cachedValue != null ) {
|
||||||
return cachedValue;
|
return cachedValue;
|
||||||
}
|
}
|
||||||
|
@ -345,8 +337,9 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
// check to see if the natural id is mutable/immutable
|
// check to see if the natural id is mutable/immutable
|
||||||
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
||||||
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
|
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
|
||||||
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
final Object dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
||||||
naturalIdHelper.cacheResolutionFromLoad(
|
|
||||||
|
naturalIdResolutions.cacheResolutionFromLoad(
|
||||||
persister,
|
persister,
|
||||||
id,
|
id,
|
||||||
dbValue
|
dbValue
|
||||||
|
@ -366,7 +359,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
for ( int i = 0; i < props.length; i++ ) {
|
for ( int i = 0; i < props.length; i++ ) {
|
||||||
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
|
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
|
||||||
}
|
}
|
||||||
naturalIdHelper.cacheResolutionFromLoad(
|
naturalIdResolutions.cacheResolutionFromLoad(
|
||||||
persister,
|
persister,
|
||||||
id,
|
id,
|
||||||
naturalIdSnapshotSubSet
|
naturalIdSnapshotSubSet
|
||||||
|
@ -1178,7 +1171,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
final boolean afterFlush = this.flushing && ! flushing;
|
final boolean afterFlush = this.flushing && ! flushing;
|
||||||
this.flushing = flushing;
|
this.flushing = flushing;
|
||||||
if ( afterFlush ) {
|
if ( afterFlush ) {
|
||||||
getNaturalIdHelper().cleanupFromSynchronizations();
|
getNaturalIdResolutions().cleanupFromSynchronizations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1936,335 +1929,15 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
|
|
||||||
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
private NaturalIdXrefDelegate naturalIdXrefDelegate;
|
private NaturalIdResolutionsImpl naturalIdResolutions;
|
||||||
|
|
||||||
private NaturalIdXrefDelegate getNaturalIdXrefDelegate() {
|
|
||||||
if ( naturalIdXrefDelegate == null ) {
|
|
||||||
this.naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
|
|
||||||
}
|
|
||||||
return naturalIdXrefDelegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
|
|
||||||
@Override
|
|
||||||
public void cacheResolutionFromLoad(
|
|
||||||
EntityPersister persister,
|
|
||||||
Object 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 journaling
|
|
||||||
// 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.
|
|
||||||
final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalIdValues );
|
|
||||||
|
|
||||||
if ( justAddedLocally && persister.hasNaturalIdCache() ) {
|
|
||||||
managedSharedResolutions( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void manageLocalResolution(
|
|
||||||
Object id,
|
|
||||||
Object naturalId,
|
|
||||||
EntityPersister persister,
|
|
||||||
CachedNaturalIdValueSource source) {
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
persister = locateProperPersister( persister );
|
|
||||||
|
|
||||||
// cache
|
|
||||||
getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalId );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void manageSharedResolution(
|
|
||||||
final Object id,
|
|
||||||
Object naturalId,
|
|
||||||
Object previousNaturalId,
|
|
||||||
EntityPersister persister,
|
|
||||||
CachedNaturalIdValueSource source) {
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !persister.hasNaturalIdCache() ) {
|
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
persister = locateProperPersister( persister );
|
|
||||||
// final Object naturalIdValues = extractNaturalIdValues( naturalId, persister );
|
|
||||||
final Object naturalIdValues = naturalId;
|
|
||||||
|
|
||||||
// final Object previousNaturalIdValues = previousNaturalId == null ? null : extractNaturalIdValues( previousNaturalId, persister );
|
|
||||||
final Object previousNaturalIdValues = previousNaturalId;
|
|
||||||
|
|
||||||
managedSharedResolutions( persister, id, naturalIdValues, previousNaturalIdValues, source );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void managedSharedResolutions(
|
|
||||||
EntityPersister persister,
|
|
||||||
final Object id,
|
|
||||||
Object naturalIdValues,
|
|
||||||
Object previousNaturalIdValues,
|
|
||||||
CachedNaturalIdValueSource source) {
|
|
||||||
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
|
||||||
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session );
|
|
||||||
|
|
||||||
final SessionFactoryImplementor factory = session.getFactory();
|
|
||||||
final StatisticsImplementor statistics = factory.getStatistics();
|
|
||||||
|
|
||||||
switch ( source ) {
|
|
||||||
case LOAD: {
|
|
||||||
if ( CacheHelper.fromSharedCache( session, naturalIdCacheKey, naturalIdCacheAccessStrategy ) != null ) {
|
|
||||||
// prevent identical re-cachings
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
|
||||||
session,
|
|
||||||
naturalIdCacheKey,
|
|
||||||
id,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( put && statistics.isStatisticsEnabled() ) {
|
|
||||||
statistics.naturalIdCachePut(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case INSERT: {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.insert( session, naturalIdCacheKey, id );
|
|
||||||
if ( put && statistics.isStatisticsEnabled() ) {
|
|
||||||
statistics.naturalIdCachePut(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
( (EventSource) session ).getActionQueue().registerProcess(
|
|
||||||
new AfterTransactionCompletionProcess() {
|
|
||||||
@Override
|
|
||||||
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
|
|
||||||
if ( success ) {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.afterInsert( session, naturalIdCacheKey, id );
|
|
||||||
if ( put && statistics.isStatisticsEnabled() ) {
|
|
||||||
statistics.naturalIdCachePut(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UPDATE: {
|
|
||||||
final Object previousCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( previousNaturalIdValues, persister, session );
|
|
||||||
if ( naturalIdCacheKey.equals( previousCacheKey ) ) {
|
|
||||||
// prevent identical re-caching, solves HHH-7309
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final SoftLock removalLock = naturalIdCacheAccessStrategy.lockItem( session, previousCacheKey, null );
|
|
||||||
naturalIdCacheAccessStrategy.remove( session, previousCacheKey);
|
|
||||||
|
|
||||||
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( session, naturalIdCacheKey, null );
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.update( session, naturalIdCacheKey, id );
|
|
||||||
if ( put && statistics.isStatisticsEnabled() ) {
|
|
||||||
statistics.naturalIdCachePut(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
( (EventSource) session ).getActionQueue().registerProcess(
|
|
||||||
new AfterTransactionCompletionProcess() {
|
|
||||||
@Override
|
|
||||||
public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) {
|
|
||||||
naturalIdCacheAccessStrategy.unlockItem( session, previousCacheKey, removalLock );
|
|
||||||
if (success) {
|
|
||||||
final boolean put = naturalIdCacheAccessStrategy.afterUpdate(
|
|
||||||
session,
|
|
||||||
naturalIdCacheKey,
|
|
||||||
id,
|
|
||||||
lock
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( put && statistics.isStatisticsEnabled() ) {
|
|
||||||
statistics.naturalIdCachePut(
|
|
||||||
StatsHelper.INSTANCE.getRootEntityRole( persister ),
|
|
||||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
naturalIdCacheAccessStrategy.unlockItem( session, naturalIdCacheKey, lock );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if ( LOG.isDebugEnabled() ) {
|
|
||||||
LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId) {
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
|
||||||
// nothing to do
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
persister = locateProperPersister( persister );
|
|
||||||
|
|
||||||
final Object localNaturalIdValues = getNaturalIdXrefDelegate().removeResolutions(
|
|
||||||
persister,
|
|
||||||
id,
|
|
||||||
naturalId
|
|
||||||
);
|
|
||||||
|
|
||||||
return localNaturalIdValues != null ? localNaturalIdValues : naturalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeSharedResolution(EntityPersister persister, Object 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 NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
|
||||||
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( 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, Object pk) {
|
|
||||||
return getNaturalIdXrefDelegate().findCachedNaturalId( locateProperPersister( persister ), pk );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object findCachedNaturalIdResolution(EntityPersister persister, Object naturalIdValues) {
|
|
||||||
return getNaturalIdXrefDelegate().findResolution( locateProperPersister( persister ), naturalIdValues );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object extractNaturalIdValues(Object[] state, EntityPersister persister) {
|
|
||||||
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
|
||||||
assert naturalIdMapping != null;
|
|
||||||
|
|
||||||
return naturalIdMapping.extractNaturalIdValues( state, getSession() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
|
|
||||||
assert naturalIdMapping != null;
|
|
||||||
|
|
||||||
return naturalIdMapping.extractNaturalIdValues( entity, session );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Object> getCachedPkResolutions(EntityPersister entityPersister) {
|
|
||||||
return getNaturalIdXrefDelegate().getCachedPkResolutions( entityPersister );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleSynchronization(EntityPersister persister, Object pk, Object entity) {
|
|
||||||
if ( !persister.hasNaturalIdentifier() ) {
|
|
||||||
// nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
persister = locateProperPersister( persister );
|
|
||||||
|
|
||||||
final Object naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister );
|
|
||||||
final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate();
|
|
||||||
final boolean changed = ! naturalIdXrefDelegate.sameAsCached(
|
|
||||||
persister,
|
|
||||||
pk,
|
|
||||||
naturalIdValuesFromCurrentObjectState
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( changed ) {
|
|
||||||
final Object cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
|
|
||||||
naturalIdXrefDelegate.cacheResolution( persister, pk, naturalIdValuesFromCurrentObjectState );
|
|
||||||
naturalIdXrefDelegate.stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
|
|
||||||
|
|
||||||
removeSharedResolution(
|
|
||||||
persister,
|
|
||||||
pk,
|
|
||||||
cachedNaturalIdValues
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanupFromSynchronizations() {
|
|
||||||
getNaturalIdXrefDelegate().unStashInvalidNaturalIdReferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleEviction(Object object, EntityPersister persister, Serializable identifier) {
|
|
||||||
getNaturalIdXrefDelegate().removeResolutions(
|
|
||||||
persister,
|
|
||||||
identifier,
|
|
||||||
findCachedNaturalId( persister, identifier )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NaturalIdHelper getNaturalIdHelper() {
|
public NaturalIdResolutions getNaturalIdResolutions() {
|
||||||
return naturalIdHelper;
|
if ( naturalIdResolutions == null ) {
|
||||||
|
this.naturalIdResolutions = new NaturalIdResolutionsImpl( this );
|
||||||
|
}
|
||||||
|
return naturalIdResolutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,10 +335,10 @@ public final class TwoPhaseLoad {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( persister.hasNaturalIdentifier() ) {
|
if ( persister.hasNaturalIdentifier() ) {
|
||||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||||
persister,
|
persister,
|
||||||
id,
|
id,
|
||||||
persistenceContext.getNaturalIdHelper().extractNaturalIdValues( hydratedState, persister )
|
persister.getNaturalIdMapping().extractNaturalIdValues( hydratedState, session )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
package org.hibernate.engine.spi;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the cached resolutions related to natural-id (to and from identifier)
|
||||||
|
*/
|
||||||
|
public interface NaturalIdResolutions {
|
||||||
|
/**
|
||||||
|
* Marker reference used to indicate that a given natural-id is invalid
|
||||||
|
*/
|
||||||
|
Object INVALID_NATURAL_ID_REFERENCE = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches a natural-id-to-identifier resolution. Handles both the local (transactional)
|
||||||
|
* and shared (second-level) caches.
|
||||||
|
*
|
||||||
|
* @return {@code true} if a new entry was actually added; {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean cacheResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a natural-id-to-identifier resolution.
|
||||||
|
*
|
||||||
|
* Handles both the local (transactional) and shared (second-level) caches.
|
||||||
|
*
|
||||||
|
* @return The cached values, if any. May be different from incoming values.
|
||||||
|
*/
|
||||||
|
Object removeResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
void cacheResolutionFromLoad(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
Object id,
|
||||||
|
Object naturalIdValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the necessary local cross-reference exists. Specifically, this
|
||||||
|
* only effects the persistence-context cache, not the L2 cache
|
||||||
|
*/
|
||||||
|
void manageLocalResolution(
|
||||||
|
Object id,
|
||||||
|
Object naturalIdValue,
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
CachedNaturalIdValueSource source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any local cross-reference, returning the previously cached value if one.
|
||||||
|
* Again, this only effects the persistence-context cache, not the L2 cache
|
||||||
|
*/
|
||||||
|
Object removeLocalResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the necessary cross-reference exists in the L2 cache
|
||||||
|
*/
|
||||||
|
void manageSharedResolution(
|
||||||
|
Object id,
|
||||||
|
Object naturalId,
|
||||||
|
Object previousNaturalId,
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
CachedNaturalIdValueSource source);
|
||||||
|
|
||||||
|
void removeSharedResolution(EntityMappingType entityDescriptor, Object id, Object naturalIdValues);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any cross-reference from the L2 cache
|
||||||
|
*/
|
||||||
|
void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the cached natural-id for the given identifier
|
||||||
|
*
|
||||||
|
* @return The cross-referenced natural-id values or {@code null}
|
||||||
|
*/
|
||||||
|
Object findCachedNaturalId(Object id, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the cached identifier for the given natural-id
|
||||||
|
*
|
||||||
|
* @return The cross-referenced primary key, {@link #INVALID_NATURAL_ID_REFERENCE} or {@code null}.
|
||||||
|
*/
|
||||||
|
Object findCachedNaturalIdResolution(Object naturalId, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all the locally cached primary key cross-reference entries for the given entity.
|
||||||
|
*
|
||||||
|
* @return The primary keys
|
||||||
|
*/
|
||||||
|
Collection<?> getCachedPkResolutions(EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 responsibility, {@link #cleanupFromSynchronizations} is the inverse process
|
||||||
|
* called after flush to clean up those entries.
|
||||||
|
*
|
||||||
|
* @see #cleanupFromSynchronizations
|
||||||
|
*/
|
||||||
|
void handleSynchronization(Object id, Object entity, EntityMappingType entityDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The clean up process of {@link #handleSynchronization}. Responsible for cleaning up the tracking
|
||||||
|
* of old values as no longer valid.
|
||||||
|
*/
|
||||||
|
void cleanupFromSynchronizations();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on {@link org.hibernate.Session#evict} to give a chance to clean up natural-id cross refs.
|
||||||
|
*/
|
||||||
|
void handleEviction(Object id, Object object, EntityMappingType entityDescriptor);
|
||||||
|
}
|
|
@ -800,150 +800,10 @@ public interface PersistenceContext {
|
||||||
*/
|
*/
|
||||||
Iterator managedEntitiesIterator();
|
Iterator managedEntitiesIterator();
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides centralized access to natural-id-related functionality.
|
|
||||||
*/
|
|
||||||
interface NaturalIdHelper {
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
void cacheResolutionFromLoad(
|
|
||||||
EntityPersister persister,
|
|
||||||
Object id,
|
|
||||||
Object naturalIdValues);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates necessary local cross-reference entries.
|
|
||||||
*/
|
|
||||||
void manageLocalResolution(
|
|
||||||
Object id,
|
|
||||||
Object naturalId,
|
|
||||||
EntityPersister persister,
|
|
||||||
CachedNaturalIdValueSource source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up local cross-reference entries.
|
|
||||||
*
|
|
||||||
* @param persister The persister representing the entity type.
|
|
||||||
* @param id The primary key value
|
|
||||||
* @param naturalId 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).
|
|
||||||
*/
|
|
||||||
Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates necessary shared (second level cache) cross-reference entries.
|
|
||||||
*/
|
|
||||||
void manageSharedResolution(
|
|
||||||
Object id,
|
|
||||||
Object naturalId,
|
|
||||||
Object previousNaturalId,
|
|
||||||
EntityPersister persister,
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
void removeSharedResolution(EntityPersister persister, Object 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}
|
|
||||||
*/
|
|
||||||
Object findCachedNaturalId(EntityPersister persister, Object 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}.
|
|
||||||
*/
|
|
||||||
Object 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
|
|
||||||
*/
|
|
||||||
Collection<?> 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
|
|
||||||
*/
|
|
||||||
void handleSynchronization(EntityPersister persister, Object pk, Object entity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The clean up process of {@link #handleSynchronization}. Responsible for cleaning up the tracking
|
|
||||||
* of old values as no longer valid.
|
|
||||||
*/
|
|
||||||
void cleanupFromSynchronizations();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called on {@link org.hibernate.Session#evict} to give a chance to clean up natural-id cross refs.
|
|
||||||
*
|
|
||||||
* @param object The entity instance.
|
|
||||||
* @param persister The entity persister
|
|
||||||
* @param identifier The entity identifier
|
|
||||||
*/
|
|
||||||
void handleEviction(Object object, EntityPersister persister, Serializable identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access to the natural-id helper for this persistence context
|
* Access to the natural-id helper for this persistence context
|
||||||
*
|
*
|
||||||
* @return This persistence context's natural-id helper
|
* @return This persistence context's natural-id helper
|
||||||
*/
|
*/
|
||||||
NaturalIdHelper getNaturalIdHelper();
|
NaturalIdResolutions getNaturalIdResolutions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.hibernate.engine.spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to put natural id values into collections. Useful mainly to
|
||||||
|
* apply equals/hashCode implementations.
|
||||||
|
*/
|
||||||
|
public interface Resolution {
|
||||||
|
Object getNaturalIdValue();
|
||||||
|
boolean isSame(Object otherValue);
|
||||||
|
}
|
|
@ -108,10 +108,10 @@ public class DefaultEvictEventListener implements EvictEventListener {
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
if ( persister.hasNaturalIdentifier() ) {
|
if ( persister.hasNaturalIdentifier() ) {
|
||||||
persistenceContext.getNaturalIdHelper().handleEviction(
|
persistenceContext.getNaturalIdResolutions().handleEviction(
|
||||||
object,
|
object,
|
||||||
persister,
|
key.getIdentifier(),
|
||||||
key.getIdentifier()
|
persister
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -537,14 +537,10 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
|
|
||||||
if ( entity != null && persister.hasNaturalIdentifier() ) {
|
if ( entity != null && persister.hasNaturalIdentifier() ) {
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
|
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||||
naturalIdHelper.cacheResolutionFromLoad(
|
|
||||||
persister,
|
persister,
|
||||||
event.getEntityId(),
|
event.getEntityId(),
|
||||||
naturalIdHelper.extractNaturalIdValues(
|
persister.getNaturalIdMapping().extractNaturalIdValues( entity, session )
|
||||||
entity,
|
|
||||||
persister
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,6 +568,16 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
event.getReadOnly()
|
event.getReadOnly()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// todo (6.0) : this is a change from previous versions
|
||||||
|
// specifically the load call previously always returned a non-proxy
|
||||||
|
// so we emulate that here. Longer term we should make the
|
||||||
|
// persister/loader/initializer sensitive to this fact - possibly
|
||||||
|
// passing LoadType along
|
||||||
|
|
||||||
|
if ( entity instanceof HibernateProxy ) {
|
||||||
|
entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
|
||||||
|
}
|
||||||
|
|
||||||
final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics();
|
final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics();
|
||||||
if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) {
|
if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) {
|
||||||
statistics.fetchEntity( event.getEntityClassName() );
|
statistics.fetchEntity( event.getEntityClassName() );
|
||||||
|
|
|
@ -94,9 +94,9 @@ public class DefaultResolveNaturalIdEventListener
|
||||||
* @return The entity from the cache, or null.
|
* @return The entity from the cache, or null.
|
||||||
*/
|
*/
|
||||||
protected Object resolveFromCache(final ResolveNaturalIdEvent event) {
|
protected Object resolveFromCache(final ResolveNaturalIdEvent event) {
|
||||||
return event.getSession().getPersistenceContextInternal().getNaturalIdHelper().findCachedNaturalIdResolution(
|
return event.getSession().getPersistenceContextInternal().getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||||
event.getEntityPersister(),
|
event.getOrderedNaturalIdValues(),
|
||||||
event.getOrderedNaturalIdValues()
|
event.getEntityPersister()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ public class DefaultResolveNaturalIdEventListener
|
||||||
//PK can be null if the entity doesn't exist
|
//PK can be null if the entity doesn't exist
|
||||||
if (pk != null) {
|
if (pk != null) {
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||||
event.getEntityPersister(),
|
event.getEntityPersister(),
|
||||||
pk,
|
pk,
|
||||||
event.getOrderedNaturalIdValues()
|
event.getOrderedNaturalIdValues()
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.loader.access;
|
package org.hibernate.loader.access;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
@ -24,7 +25,7 @@ import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
|
|
||||||
import static org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE;
|
import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -97,7 +98,8 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = context.getSession().getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = context.getSession().getPersistenceContextInternal();
|
||||||
for ( Object pk : persistenceContext.getNaturalIdHelper().getCachedPkResolutions( entityPersister() ) ) {
|
final Collection<?> cachedPkResolutions = persistenceContext.getNaturalIdResolutions().getCachedPkResolutions( entityPersister() );
|
||||||
|
for ( Object pk : cachedPkResolutions ) {
|
||||||
final EntityKey entityKey = context.getSession().generateEntityKey( pk, entityPersister() );
|
final EntityKey entityKey = context.getSession().generateEntityKey( pk, entityPersister() );
|
||||||
final Object entity = persistenceContext.getEntity( entityKey );
|
final Object entity = persistenceContext.getEntity( entityKey );
|
||||||
final EntityEntry entry = persistenceContext.getEntry( entity );
|
final EntityEntry entry = persistenceContext.getEntry( entity );
|
||||||
|
@ -122,10 +124,10 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
persistenceContext.getNaturalIdHelper().handleSynchronization(
|
persistenceContext.getNaturalIdResolutions().handleSynchronization(
|
||||||
entityPersister(),
|
|
||||||
pk,
|
pk,
|
||||||
entity
|
entity,
|
||||||
|
entityPersister()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,9 +142,9 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
||||||
final SessionImplementor session = context.getSession();
|
final SessionImplementor session = context.getSession();
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
|
|
||||||
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
|
final Object cachedResolution = persistenceContext.getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||||
entityPersister(),
|
normalizedNaturalIdValue,
|
||||||
normalizedNaturalIdValue
|
entityPersister()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
||||||
|
@ -177,9 +179,9 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
||||||
final SessionImplementor session = context.getSession();
|
final SessionImplementor session = context.getSession();
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
|
|
||||||
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
|
final Object cachedResolution = persistenceContext.getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||||
entityPersister(),
|
normalizedNaturalIdValue,
|
||||||
normalizedNaturalIdValue
|
entityPersister()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
||||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.metamodel.mapping;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -26,7 +25,6 @@ import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
import org.hibernate.engine.spi.CascadeStyle;
|
import org.hibernate.engine.spi.CascadeStyle;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.internal.util.MutableInteger;
|
|
||||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||||
import org.hibernate.mapping.Any;
|
import org.hibernate.mapping.Any;
|
||||||
import org.hibernate.mapping.BasicValue;
|
import org.hibernate.mapping.BasicValue;
|
||||||
|
@ -226,7 +224,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
||||||
(BasicType<?>) subtype,
|
(BasicType<?>) subtype,
|
||||||
containingTableExpression,
|
containingTableExpression,
|
||||||
columnExpression,
|
columnExpression,
|
||||||
false,
|
selectable.isFormula(),
|
||||||
selectable.getCustomReadExpression(),
|
selectable.getCustomReadExpression(),
|
||||||
selectable.getCustomWriteExpression(),
|
selectable.getCustomWriteExpression(),
|
||||||
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
|
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
|
||||||
|
@ -534,14 +532,11 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
||||||
final Object[] values = (Object[]) domainValue;
|
final Object[] values = (Object[]) domainValue;
|
||||||
assert values.length == attributeMappings.size();
|
assert values.length == attributeMappings.size();
|
||||||
|
|
||||||
final MutableInteger positionRef = new MutableInteger();
|
for ( int i = 0; i < attributeMappings.size(); i++ ) {
|
||||||
attributeMappings.forEach(
|
final AttributeMapping attributeMapping = attributeMappings.get( i );
|
||||||
(attributeMapping) -> {
|
final Object attributeValue = values[ i ];
|
||||||
final int position = positionRef.getAndIncrement();
|
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
|
||||||
final Object attributeValue = values[ position ];
|
}
|
||||||
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
attributeMappings.forEach(
|
attributeMappings.forEach(
|
||||||
|
@ -555,16 +550,13 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object disassemble(Object value, SharedSessionContractImplementor session) {
|
public Object disassemble(Object value, SharedSessionContractImplementor session) {
|
||||||
final Collection<AttributeMapping> attributeMappings = getAttributeMappings();
|
final List<AttributeMapping> attributeMappings = getAttributeMappings();
|
||||||
|
|
||||||
Object[] result = new Object[attributeMappings.size()];
|
final Object[] result = new Object[ attributeMappings.size() ];
|
||||||
int i = 0;
|
for ( int i = 0; i < attributeMappings.size(); i++ ) {
|
||||||
final Iterator<AttributeMapping> iterator = attributeMappings.iterator();
|
final AttributeMapping attributeMapping = attributeMappings.get( i );
|
||||||
while ( iterator.hasNext() ) {
|
Object o = attributeMapping.getPropertyAccess().getGetter().get( value );
|
||||||
AttributeMapping mapping = iterator.next();
|
result[i] = attributeMapping.disassemble( o, session );
|
||||||
Object o = mapping.getPropertyAccess().getGetter().get( value );
|
|
||||||
result[i] = mapping.disassemble( o, session );
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -201,7 +201,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
|
||||||
|
|
||||||
final Object snapshot = loadedState == null
|
final Object snapshot = loadedState == null
|
||||||
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
||||||
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
|
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
|
||||||
final Object[] previousNaturalId = (Object[]) snapshot;
|
final Object[] previousNaturalId = (Object[]) snapshot;
|
||||||
|
|
||||||
assert naturalId.length == getNaturalIdAttributes().size();
|
assert naturalId.length == getNaturalIdAttributes().size();
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
||||||
final Object naturalId = extractNaturalIdValues( currentState, session );
|
final Object naturalId = extractNaturalIdValues( currentState, session );
|
||||||
final Object snapshot = loadedState == null
|
final Object snapshot = loadedState == null
|
||||||
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
||||||
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
|
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
|
||||||
|
|
||||||
if ( ! areEqual( naturalId, snapshot, session ) ) {
|
if ( ! areEqual( naturalId, snapshot, session ) ) {
|
||||||
throw new HibernateException(
|
throw new HibernateException(
|
||||||
|
|
|
@ -87,8 +87,8 @@ import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.Mapping;
|
import org.hibernate.engine.spi.Mapping;
|
||||||
|
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper;
|
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||||
|
@ -5012,7 +5012,7 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
final NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
|
final NaturalIdResolutions naturalIdResolutions = persistenceContext.getNaturalIdResolutions();
|
||||||
final Object id = getIdentifier( entity, session );
|
final Object id = getIdentifier( entity, session );
|
||||||
|
|
||||||
// for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the
|
// for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the
|
||||||
|
@ -5023,12 +5023,14 @@ public abstract class AbstractEntityPersister
|
||||||
naturalIdSnapshot = null;
|
naturalIdSnapshot = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
naturalIdSnapshot = naturalIdHelper.extractNaturalIdValues( entitySnapshot, this );
|
naturalIdSnapshot = naturalIdMapping.extractNaturalIdValues( entitySnapshot, session );
|
||||||
}
|
}
|
||||||
|
|
||||||
naturalIdHelper.removeSharedResolution( this, id, naturalIdSnapshot );
|
naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this );
|
||||||
naturalIdHelper.manageLocalResolution(
|
naturalIdResolutions.manageLocalResolution(
|
||||||
id, naturalIdHelper.extractNaturalIdValues( entity, this ), this,
|
id,
|
||||||
|
naturalIdMapping.extractNaturalIdValues( entity, session ),
|
||||||
|
this,
|
||||||
CachedNaturalIdValueSource.UPDATE
|
CachedNaturalIdValueSource.UPDATE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5700,7 +5702,7 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object[] getNaturalIdentifierSnapshot(Object id, SharedSessionContractImplementor session) {
|
public Object getNaturalIdentifierSnapshot(Object id, SharedSessionContractImplementor session) {
|
||||||
verifyHasNaturalId();
|
verifyHasNaturalId();
|
||||||
|
|
||||||
if ( LOG.isTraceEnabled() ) {
|
if ( LOG.isTraceEnabled() ) {
|
||||||
|
@ -5712,12 +5714,7 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object result = getNaturalIdLoader().resolveIdToNaturalId( id, session );
|
final Object result = getNaturalIdLoader().resolveIdToNaturalId( id, session );
|
||||||
if ( result instanceof Object[] ) {
|
return result;
|
||||||
return (Object[]) result;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new Object[] { result };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -397,7 +397,7 @@ public interface EntityPersister
|
||||||
* @param session The session from which the request originated.
|
* @param session The session from which the request originated.
|
||||||
* @return The natural-id snapshot.
|
* @return The natural-id snapshot.
|
||||||
*/
|
*/
|
||||||
Object[] getNaturalIdentifierSnapshot(Object id, SharedSessionContractImplementor session);
|
Object getNaturalIdentifierSnapshot(Object id, SharedSessionContractImplementor session);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine which identifier generation strategy is used for this entity.
|
* Determine which identifier generation strategy is used for this entity.
|
||||||
|
|
|
@ -573,25 +573,33 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
session,
|
session,
|
||||||
persistenceContext
|
persistenceContext
|
||||||
);
|
);
|
||||||
intializeEntity( instance, rowProcessingState, session, persistenceContext );
|
initializeEntity( instance, rowProcessingState, session, persistenceContext );
|
||||||
hibernateLazyInitializer.setImplementation( instance );
|
hibernateLazyInitializer.setImplementation( instance );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
intializeEntity( entityInstance, rowProcessingState, session, persistenceContext );
|
initializeEntity( entityInstance, rowProcessingState, session, persistenceContext );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void intializeEntity(
|
private void initializeEntity(
|
||||||
Object toInitialize,
|
Object toInitialize,
|
||||||
RowProcessingState rowProcessingState,
|
RowProcessingState rowProcessingState,
|
||||||
SharedSessionContractImplementor session,
|
SharedSessionContractImplementor session,
|
||||||
PersistenceContext persistenceContext) {
|
PersistenceContext persistenceContext) {
|
||||||
final Object entity = persistenceContext.getEntity( entityKey );
|
final EntityEntry entry = persistenceContext.getEntry( toInitialize );
|
||||||
|
if ( entry != null ) {
|
||||||
if ( entity != null ) {
|
if ( entry.getStatus() != Status.LOADING ) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Object entity = persistenceContext.getEntity( entityKey );
|
||||||
|
assert entity == null || entity == toInitialize;
|
||||||
|
//
|
||||||
|
// if ( entity != null ) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
final Serializable entityIdentifier = entityKey.getIdentifier();
|
final Serializable entityIdentifier = entityKey.getIdentifier();
|
||||||
|
|
||||||
if ( EntityLoadingLogger.TRACE_ENABLED ) {
|
if ( EntityLoadingLogger.TRACE_ENABLED ) {
|
||||||
|
@ -620,10 +628,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
|
|
||||||
concreteDescriptor.setPropertyValues( toInitialize, resolvedEntityState );
|
concreteDescriptor.setPropertyValues( toInitialize, resolvedEntityState );
|
||||||
|
|
||||||
persistenceContext.addEntity(
|
persistenceContext.addEntity( entityKey, toInitialize );
|
||||||
entityKey,
|
|
||||||
toInitialize
|
|
||||||
);
|
|
||||||
|
|
||||||
final Object version;
|
final Object version;
|
||||||
|
|
||||||
|
@ -668,7 +673,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CacheEntry entry = concreteDescriptor.buildCacheEntry( toInitialize, resolvedEntityState, version, session );
|
final CacheEntry cacheEntry = concreteDescriptor.buildCacheEntry( toInitialize, resolvedEntityState, version, session );
|
||||||
final Object cacheKey = cacheAccess.generateCacheKey(
|
final Object cacheKey = cacheAccess.generateCacheKey(
|
||||||
entityIdentifier,
|
entityIdentifier,
|
||||||
rootEntityDescriptor,
|
rootEntityDescriptor,
|
||||||
|
@ -686,7 +691,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
cacheAccess.update(
|
cacheAccess.update(
|
||||||
session,
|
session,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
rootEntityDescriptor.getCacheEntryStructure().structure( entry ),
|
rootEntityDescriptor.getCacheEntryStructure().structure( cacheEntry ),
|
||||||
version,
|
version,
|
||||||
version
|
version
|
||||||
);
|
);
|
||||||
|
@ -698,7 +703,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
final boolean put = cacheAccess.putFromLoad(
|
final boolean put = cacheAccess.putFromLoad(
|
||||||
session,
|
session,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
rootEntityDescriptor.getCacheEntryStructure().structure( entry ),
|
rootEntityDescriptor.getCacheEntryStructure().structure( cacheEntry ),
|
||||||
version,
|
version,
|
||||||
//useMinimalPuts( session, entityEntry )
|
//useMinimalPuts( session, entityEntry )
|
||||||
false
|
false
|
||||||
|
@ -715,11 +720,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( entityDescriptor.getNaturalIdMapping() != null ) {
|
if ( entityDescriptor.getNaturalIdMapping() != null ) {
|
||||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||||
entityDescriptor,
|
entityDescriptor,
|
||||||
entityIdentifier,
|
entityIdentifier,
|
||||||
persistenceContext.getNaturalIdHelper()
|
entityDescriptor.getNaturalIdMapping().extractNaturalIdValues( resolvedEntityState, session )
|
||||||
.extractNaturalIdValues( resolvedEntityState, entityDescriptor )
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue