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:
Steve Ebersole 2021-01-28 11:53:30 -06:00
parent 270fba830a
commit 70baa0b659
21 changed files with 1011 additions and 1059 deletions

View File

@ -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,

View File

@ -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();

View File

@ -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,

View File

@ -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
);

View File

@ -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() );
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 )
);
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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
);
}

View File

@ -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() );

View File

@ -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()

View File

@ -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 ) {

View File

@ -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;

View File

@ -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();

View File

@ -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(

View File

@ -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;
}

View File

@ -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.

View File

@ -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 )
);
}