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.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.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
|
||||
final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
|
||||
if ( naturalIdMapping != null ) {
|
||||
getSession().getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
|
||||
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||
getId(),
|
||||
naturalIdMapping.extractNaturalIdValues( state, getSession() ),
|
||||
getPersister(),
|
||||
|
@ -188,10 +187,9 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
|||
|
||||
final Object naturalIdValues = naturalIdMapping.extractNaturalIdValues( state, getSession() );
|
||||
|
||||
final PersistenceContext.NaturalIdHelper naturalIdHelper = getSession().getPersistenceContextInternal().getNaturalIdHelper();
|
||||
if ( isEarlyInsert() ) {
|
||||
// with early insert, we still need to add a local (transactional) natural id cross-reference
|
||||
naturalIdHelper.manageLocalResolution(
|
||||
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||
generatedId,
|
||||
naturalIdValues,
|
||||
getPersister(),
|
||||
|
@ -199,7 +197,7 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
|||
);
|
||||
}
|
||||
// after save, we need to manage the shared cache entries
|
||||
naturalIdHelper.manageSharedResolution(
|
||||
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageSharedResolution(
|
||||
generatedId,
|
||||
naturalIdValues,
|
||||
null,
|
||||
|
|
|
@ -64,10 +64,10 @@ public class EntityDeleteAction extends EntityAction {
|
|||
this.naturalIdMapping = persister.getNaturalIdMapping();
|
||||
|
||||
if ( naturalIdMapping != null ) {
|
||||
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdHelper().removeLocalResolution(
|
||||
getPersister(),
|
||||
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdResolutions().removeLocalResolution(
|
||||
getId(),
|
||||
naturalIdMapping.extractNaturalIdValues( state, session )
|
||||
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||
getPersister()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ public class EntityDeleteAction extends EntityAction {
|
|||
persister.getCacheAccessStrategy().remove( session, ck);
|
||||
}
|
||||
|
||||
persistenceContext.getNaturalIdHelper().removeSharedResolution( persister, id, naturalIdValues );
|
||||
persistenceContext.getNaturalIdResolutions().removeSharedResolution( persister, id, naturalIdValues );
|
||||
|
||||
postDelete();
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ public class EntityUpdateAction extends EntityAction {
|
|||
}
|
||||
else {
|
||||
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, naturalIdMapping, id, previousState, session );
|
||||
session.getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
|
||||
session.getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
|
||||
id,
|
||||
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||
persister,
|
||||
|
@ -224,7 +224,7 @@ public class EntityUpdateAction extends EntityAction {
|
|||
}
|
||||
|
||||
if ( naturalIdMapping != null ) {
|
||||
session.getPersistenceContextInternal().getNaturalIdHelper().manageSharedResolution(
|
||||
session.getPersistenceContextInternal().getNaturalIdResolutions().manageSharedResolution(
|
||||
id,
|
||||
naturalIdMapping.extractNaturalIdValues( state, session ),
|
||||
previousNaturalIdValues,
|
||||
|
|
|
@ -408,7 +408,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
|
|||
}
|
||||
setStatus( Status.MANAGED );
|
||||
loadedState = getPersister().getPropertyValues( entity );
|
||||
getPersistenceContext().getNaturalIdHelper().manageLocalResolution(
|
||||
getPersistenceContext().getNaturalIdResolutions().manageLocalResolution(
|
||||
id, loadedState, persister,
|
||||
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.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -33,22 +32,19 @@ import org.hibernate.MappingException;
|
|||
import org.hibernate.NonUniqueObjectException;
|
||||
import org.hibernate.PersistentObjectException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
|
||||
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.engine.spi.AssociationKey;
|
||||
import org.hibernate.engine.spi.BatchFetchQueue;
|
||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.CollectionEntry;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
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.SharedSessionContractImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
|
||||
import org.hibernate.internal.util.collections.IdentityMap;
|
||||
import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
||||
import org.hibernate.metamodel.spi.MetamodelImplementor;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
@ -69,8 +63,6 @@ import org.hibernate.pretty.MessageHelper;
|
|||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
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.jboss.logging.Logger;
|
||||
|
@ -268,7 +260,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
if ( loadContexts != null ) {
|
||||
loadContexts.cleanup();
|
||||
}
|
||||
naturalIdXrefDelegate = null;
|
||||
naturalIdResolutions = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -337,7 +329,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
persister = locateProperPersister( persister );
|
||||
|
||||
// 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 ) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
@ -345,8 +337,9 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
// check to see if the natural id is mutable/immutable
|
||||
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
|
||||
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
|
||||
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
||||
naturalIdHelper.cacheResolutionFromLoad(
|
||||
final Object dbValue = persister.getNaturalIdentifierSnapshot( id, session );
|
||||
|
||||
naturalIdResolutions.cacheResolutionFromLoad(
|
||||
persister,
|
||||
id,
|
||||
dbValue
|
||||
|
@ -366,7 +359,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
for ( int i = 0; i < props.length; i++ ) {
|
||||
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
|
||||
}
|
||||
naturalIdHelper.cacheResolutionFromLoad(
|
||||
naturalIdResolutions.cacheResolutionFromLoad(
|
||||
persister,
|
||||
id,
|
||||
naturalIdSnapshotSubSet
|
||||
|
@ -1178,7 +1171,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
final boolean afterFlush = this.flushing && ! flushing;
|
||||
this.flushing = flushing;
|
||||
if ( afterFlush ) {
|
||||
getNaturalIdHelper().cleanupFromSynchronizations();
|
||||
getNaturalIdResolutions().cleanupFromSynchronizations();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1936,335 +1929,15 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
|
||||
// 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
|
||||
public NaturalIdHelper getNaturalIdHelper() {
|
||||
return naturalIdHelper;
|
||||
public NaturalIdResolutions getNaturalIdResolutions() {
|
||||
if ( naturalIdResolutions == null ) {
|
||||
this.naturalIdResolutions = new NaturalIdResolutionsImpl( this );
|
||||
}
|
||||
return naturalIdResolutions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -335,10 +335,10 @@ public final class TwoPhaseLoad {
|
|||
}
|
||||
|
||||
if ( persister.hasNaturalIdentifier() ) {
|
||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
||||
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||
persister,
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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();
|
||||
if ( persister.hasNaturalIdentifier() ) {
|
||||
persistenceContext.getNaturalIdHelper().handleEviction(
|
||||
persistenceContext.getNaturalIdResolutions().handleEviction(
|
||||
object,
|
||||
persister,
|
||||
key.getIdentifier()
|
||||
key.getIdentifier(),
|
||||
persister
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -537,14 +537,10 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
|||
|
||||
if ( entity != null && persister.hasNaturalIdentifier() ) {
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
|
||||
naturalIdHelper.cacheResolutionFromLoad(
|
||||
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||
persister,
|
||||
event.getEntityId(),
|
||||
naturalIdHelper.extractNaturalIdValues(
|
||||
entity,
|
||||
persister
|
||||
)
|
||||
persister.getNaturalIdMapping().extractNaturalIdValues( entity, session )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -572,6 +568,16 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
|||
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();
|
||||
if ( event.isAssociationFetch() && statistics.isStatisticsEnabled() ) {
|
||||
statistics.fetchEntity( event.getEntityClassName() );
|
||||
|
|
|
@ -94,9 +94,9 @@ public class DefaultResolveNaturalIdEventListener
|
|||
* @return The entity from the cache, or null.
|
||||
*/
|
||||
protected Object resolveFromCache(final ResolveNaturalIdEvent event) {
|
||||
return event.getSession().getPersistenceContextInternal().getNaturalIdHelper().findCachedNaturalIdResolution(
|
||||
event.getEntityPersister(),
|
||||
event.getOrderedNaturalIdValues()
|
||||
return event.getSession().getPersistenceContextInternal().getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||
event.getOrderedNaturalIdValues(),
|
||||
event.getEntityPersister()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,7 @@ public class DefaultResolveNaturalIdEventListener
|
|||
//PK can be null if the entity doesn't exist
|
||||
if (pk != null) {
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
||||
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||
event.getEntityPersister(),
|
||||
pk,
|
||||
event.getOrderedNaturalIdValues()
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.loader.access;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -24,7 +25,7 @@ import org.hibernate.persister.entity.EntityPersister;
|
|||
import org.hibernate.proxy.HibernateProxy;
|
||||
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
|
||||
|
@ -97,7 +98,8 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
|||
}
|
||||
|
||||
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 Object entity = persistenceContext.getEntity( entityKey );
|
||||
final EntityEntry entry = persistenceContext.getEntry( entity );
|
||||
|
@ -122,10 +124,10 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
|||
continue;
|
||||
}
|
||||
|
||||
persistenceContext.getNaturalIdHelper().handleSynchronization(
|
||||
entityPersister(),
|
||||
persistenceContext.getNaturalIdResolutions().handleSynchronization(
|
||||
pk,
|
||||
entity
|
||||
entity,
|
||||
entityPersister()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -140,9 +142,9 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
|||
final SessionImplementor session = context.getSession();
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
|
||||
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
|
||||
entityPersister(),
|
||||
normalizedNaturalIdValue
|
||||
final Object cachedResolution = persistenceContext.getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||
normalizedNaturalIdValue,
|
||||
entityPersister()
|
||||
);
|
||||
|
||||
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
||||
|
@ -177,9 +179,9 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
|
|||
final SessionImplementor session = context.getSession();
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
|
||||
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
|
||||
entityPersister(),
|
||||
normalizedNaturalIdValue
|
||||
final Object cachedResolution = persistenceContext.getNaturalIdResolutions().findCachedNaturalIdResolution(
|
||||
normalizedNaturalIdValue,
|
||||
entityPersister()
|
||||
);
|
||||
|
||||
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.metamodel.mapping;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
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.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.MutableInteger;
|
||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
|
@ -226,7 +224,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
|||
(BasicType<?>) subtype,
|
||||
containingTableExpression,
|
||||
columnExpression,
|
||||
false,
|
||||
selectable.isFormula(),
|
||||
selectable.getCustomReadExpression(),
|
||||
selectable.getCustomWriteExpression(),
|
||||
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
|
||||
|
@ -534,14 +532,11 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
|||
final Object[] values = (Object[]) domainValue;
|
||||
assert values.length == attributeMappings.size();
|
||||
|
||||
final MutableInteger positionRef = new MutableInteger();
|
||||
attributeMappings.forEach(
|
||||
(attributeMapping) -> {
|
||||
final int position = positionRef.getAndIncrement();
|
||||
final Object attributeValue = values[ position ];
|
||||
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
|
||||
}
|
||||
);
|
||||
for ( int i = 0; i < attributeMappings.size(); i++ ) {
|
||||
final AttributeMapping attributeMapping = attributeMappings.get( i );
|
||||
final Object attributeValue = values[ i ];
|
||||
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
|
||||
}
|
||||
}
|
||||
else {
|
||||
attributeMappings.forEach(
|
||||
|
@ -555,16 +550,13 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
|
|||
|
||||
@Override
|
||||
public Object disassemble(Object value, SharedSessionContractImplementor session) {
|
||||
final Collection<AttributeMapping> attributeMappings = getAttributeMappings();
|
||||
final List<AttributeMapping> attributeMappings = getAttributeMappings();
|
||||
|
||||
Object[] result = new Object[attributeMappings.size()];
|
||||
int i = 0;
|
||||
final Iterator<AttributeMapping> iterator = attributeMappings.iterator();
|
||||
while ( iterator.hasNext() ) {
|
||||
AttributeMapping mapping = iterator.next();
|
||||
Object o = mapping.getPropertyAccess().getGetter().get( value );
|
||||
result[i] = mapping.disassemble( o, session );
|
||||
i++;
|
||||
final Object[] result = new Object[ attributeMappings.size() ];
|
||||
for ( int i = 0; i < attributeMappings.size(); i++ ) {
|
||||
final AttributeMapping attributeMapping = attributeMappings.get( i );
|
||||
Object o = attributeMapping.getPropertyAccess().getGetter().get( value );
|
||||
result[i] = attributeMapping.disassemble( o, session );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -201,7 +201,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
|
|||
|
||||
final Object snapshot = loadedState == null
|
||||
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
||||
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
|
||||
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
|
||||
final Object[] previousNaturalId = (Object[]) snapshot;
|
||||
|
||||
assert naturalId.length == getNaturalIdAttributes().size();
|
||||
|
|
|
@ -64,7 +64,7 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
|
|||
final Object naturalId = extractNaturalIdValues( currentState, session );
|
||||
final Object snapshot = loadedState == null
|
||||
? persistenceContext.getNaturalIdSnapshot( id, persister )
|
||||
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
|
||||
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
|
||||
|
||||
if ( ! areEqual( naturalId, snapshot, session ) ) {
|
||||
throw new HibernateException(
|
||||
|
|
|
@ -87,8 +87,8 @@ import org.hibernate.engine.spi.EntityKey;
|
|||
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.Mapping;
|
||||
import org.hibernate.engine.spi.NaturalIdResolutions;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
|
@ -5012,7 +5012,7 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
final NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
|
||||
final NaturalIdResolutions naturalIdResolutions = persistenceContext.getNaturalIdResolutions();
|
||||
final Object id = getIdentifier( entity, session );
|
||||
|
||||
// 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;
|
||||
}
|
||||
else {
|
||||
naturalIdSnapshot = naturalIdHelper.extractNaturalIdValues( entitySnapshot, this );
|
||||
naturalIdSnapshot = naturalIdMapping.extractNaturalIdValues( entitySnapshot, session );
|
||||
}
|
||||
|
||||
naturalIdHelper.removeSharedResolution( this, id, naturalIdSnapshot );
|
||||
naturalIdHelper.manageLocalResolution(
|
||||
id, naturalIdHelper.extractNaturalIdValues( entity, this ), this,
|
||||
naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this );
|
||||
naturalIdResolutions.manageLocalResolution(
|
||||
id,
|
||||
naturalIdMapping.extractNaturalIdValues( entity, session ),
|
||||
this,
|
||||
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();
|
||||
|
||||
if ( LOG.isTraceEnabled() ) {
|
||||
|
@ -5712,12 +5714,7 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
final Object result = getNaturalIdLoader().resolveIdToNaturalId( id, session );
|
||||
if ( result instanceof Object[] ) {
|
||||
return (Object[]) result;
|
||||
}
|
||||
else {
|
||||
return new Object[] { result };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -397,7 +397,7 @@ public interface EntityPersister
|
|||
* @param session The session from which the request originated.
|
||||
* @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.
|
||||
|
|
|
@ -573,25 +573,33 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
session,
|
||||
persistenceContext
|
||||
);
|
||||
intializeEntity( instance, rowProcessingState, session, persistenceContext );
|
||||
initializeEntity( instance, rowProcessingState, session, persistenceContext );
|
||||
hibernateLazyInitializer.setImplementation( instance );
|
||||
}
|
||||
else {
|
||||
intializeEntity( entityInstance, rowProcessingState, session, persistenceContext );
|
||||
initializeEntity( entityInstance, rowProcessingState, session, persistenceContext );
|
||||
}
|
||||
}
|
||||
|
||||
private void intializeEntity(
|
||||
private void initializeEntity(
|
||||
Object toInitialize,
|
||||
RowProcessingState rowProcessingState,
|
||||
SharedSessionContractImplementor session,
|
||||
PersistenceContext persistenceContext) {
|
||||
final Object entity = persistenceContext.getEntity( entityKey );
|
||||
|
||||
if ( entity != null ) {
|
||||
return;
|
||||
final EntityEntry entry = persistenceContext.getEntry( toInitialize );
|
||||
if ( entry != null ) {
|
||||
if ( entry.getStatus() != Status.LOADING ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Object entity = persistenceContext.getEntity( entityKey );
|
||||
assert entity == null || entity == toInitialize;
|
||||
//
|
||||
// if ( entity != null ) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
final Serializable entityIdentifier = entityKey.getIdentifier();
|
||||
|
||||
if ( EntityLoadingLogger.TRACE_ENABLED ) {
|
||||
|
@ -620,10 +628,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
|
||||
concreteDescriptor.setPropertyValues( toInitialize, resolvedEntityState );
|
||||
|
||||
persistenceContext.addEntity(
|
||||
entityKey,
|
||||
toInitialize
|
||||
);
|
||||
persistenceContext.addEntity( entityKey, toInitialize );
|
||||
|
||||
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(
|
||||
entityIdentifier,
|
||||
rootEntityDescriptor,
|
||||
|
@ -686,7 +691,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
cacheAccess.update(
|
||||
session,
|
||||
cacheKey,
|
||||
rootEntityDescriptor.getCacheEntryStructure().structure( entry ),
|
||||
rootEntityDescriptor.getCacheEntryStructure().structure( cacheEntry ),
|
||||
version,
|
||||
version
|
||||
);
|
||||
|
@ -698,7 +703,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
final boolean put = cacheAccess.putFromLoad(
|
||||
session,
|
||||
cacheKey,
|
||||
rootEntityDescriptor.getCacheEntryStructure().structure( entry ),
|
||||
rootEntityDescriptor.getCacheEntryStructure().structure( cacheEntry ),
|
||||
version,
|
||||
//useMinimalPuts( session, entityEntry )
|
||||
false
|
||||
|
@ -715,11 +720,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
|
|||
}
|
||||
|
||||
if ( entityDescriptor.getNaturalIdMapping() != null ) {
|
||||
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
|
||||
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
|
||||
entityDescriptor,
|
||||
entityIdentifier,
|
||||
persistenceContext.getNaturalIdHelper()
|
||||
.extractNaturalIdValues( resolvedEntityState, entityDescriptor )
|
||||
entityDescriptor.getNaturalIdMapping().extractNaturalIdValues( resolvedEntityState, session )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue