HHH-14409 : Internal format of natural-id values

At the moment, internally the value of a natural-id is always kept as an array.  For simple natural-ids that means creating an unnecessary array to wrap the simple value.  Change this to allow Object to allow for these simple values
This commit is contained in:
Steve Ebersole 2021-01-14 08:51:59 -06:00
parent 0196911c8d
commit 3ecc2550df
29 changed files with 517 additions and 375 deletions

View File

@ -17,6 +17,7 @@
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.Status;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
/** /**
@ -163,13 +164,15 @@ public void afterDeserialize(SharedSessionContractImplementor session) {
*/ */
protected void handleNaturalIdPreSaveNotifications() { protected void handleNaturalIdPreSaveNotifications() {
// before save, we need to add a local (transactional) natural id cross-reference // before save, we need to add a local (transactional) natural id cross-reference
getSession().getPersistenceContextInternal().getNaturalIdHelper().manageLocalNaturalIdCrossReference( final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
getPersister(), if ( naturalIdMapping != null ) {
getId(), getSession().getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
state, getId(),
null, naturalIdMapping.extractNaturalIdValues( state, getSession() ),
CachedNaturalIdValueSource.INSERT getPersister(),
); CachedNaturalIdValueSource.INSERT
);
}
} }
/** /**
@ -178,23 +181,29 @@ protected void handleNaturalIdPreSaveNotifications() {
* @param generatedId The generated entity identifier * @param generatedId The generated entity identifier
*/ */
public void handleNaturalIdPostSaveNotifications(Object generatedId) { public void handleNaturalIdPostSaveNotifications(Object generatedId) {
final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
if ( naturalIdMapping == null ) {
return;
}
final Object naturalIdValues = naturalIdMapping.extractNaturalIdValues( state, getSession() );
final PersistenceContext.NaturalIdHelper naturalIdHelper = getSession().getPersistenceContextInternal().getNaturalIdHelper(); final PersistenceContext.NaturalIdHelper naturalIdHelper = getSession().getPersistenceContextInternal().getNaturalIdHelper();
if ( isEarlyInsert() ) { if ( isEarlyInsert() ) {
// with early insert, we still need to add a local (transactional) natural id cross-reference // with early insert, we still need to add a local (transactional) natural id cross-reference
naturalIdHelper.manageLocalNaturalIdCrossReference( naturalIdHelper.manageLocalResolution(
getPersister(),
generatedId, generatedId,
state, naturalIdValues,
null, getPersister(),
CachedNaturalIdValueSource.INSERT CachedNaturalIdValueSource.INSERT
); );
} }
// after save, we need to manage the shared cache entries // after save, we need to manage the shared cache entries
naturalIdHelper.manageSharedNaturalIdCrossReference( naturalIdHelper.manageSharedResolution(
getPersister(),
generatedId, generatedId,
state, naturalIdValues,
null, null,
getPersister(),
CachedNaturalIdValueSource.INSERT CachedNaturalIdValueSource.INSERT
); );
} }

View File

@ -21,6 +21,7 @@
import org.hibernate.event.spi.PostDeleteEventListener; import org.hibernate.event.spi.PostDeleteEventListener;
import org.hibernate.event.spi.PreDeleteEvent; import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreDeleteEventListener; import org.hibernate.event.spi.PreDeleteEventListener;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
@ -33,7 +34,9 @@ public class EntityDeleteAction extends EntityAction {
private final Object[] state; private final Object[] state;
private SoftLock lock; private SoftLock lock;
private Object[] naturalIdValues;
private final NaturalIdMapping naturalIdMapping;
private Object naturalIdValues;
/** /**
* Constructs an EntityDeleteAction. * Constructs an EntityDeleteAction.
@ -58,12 +61,15 @@ public EntityDeleteAction(
this.isCascadeDeleteEnabled = isCascadeDeleteEnabled; this.isCascadeDeleteEnabled = isCascadeDeleteEnabled;
this.state = state; this.state = state;
// before remove we need to remove the local (transactional) natural id cross-reference this.naturalIdMapping = persister.getNaturalIdMapping();
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdHelper().removeLocalNaturalIdCrossReference(
getPersister(), if ( naturalIdMapping != null ) {
getId(), naturalIdValues = session.getPersistenceContextInternal().getNaturalIdHelper().removeLocalResolution(
state getPersister(),
); getId(),
naturalIdMapping.extractNaturalIdValues( state, session )
);
}
} }
public Object getVersion() { public Object getVersion() {
@ -78,7 +84,7 @@ public Object[] getState() {
return state; return state;
} }
protected Object[] getNaturalIdValues() { protected Object getNaturalIdValues() {
return naturalIdValues; return naturalIdValues;
} }
@ -139,7 +145,7 @@ public void execute() throws HibernateException {
persister.getCacheAccessStrategy().remove( session, ck); persister.getCacheAccessStrategy().remove( session, ck);
} }
persistenceContext.getNaturalIdHelper().removeSharedNaturalIdCrossReference( persister, id, naturalIdValues ); persistenceContext.getNaturalIdHelper().removeSharedResolution( persister, id, naturalIdValues );
postDelete(); postDelete();

View File

@ -27,6 +27,7 @@
import org.hibernate.event.spi.PostUpdateEventListener; import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.event.spi.PreUpdateEvent; import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
@ -42,11 +43,13 @@ public class EntityUpdateAction extends EntityAction {
private final int[] dirtyFields; private final int[] dirtyFields;
private final boolean hasDirtyCollection; private final boolean hasDirtyCollection;
private final Object rowId; private final Object rowId;
private final Object[] previousNaturalIdValues;
private Object nextVersion; private Object nextVersion;
private Object cacheEntry; private Object cacheEntry;
private SoftLock lock; private SoftLock lock;
private final NaturalIdMapping naturalIdMapping;
private final Object previousNaturalIdValues;
/** /**
* Constructs an EntityUpdateAction * Constructs an EntityUpdateAction
* @param id The entity identifier * @param id The entity identifier
@ -82,24 +85,23 @@ public EntityUpdateAction(
this.hasDirtyCollection = hasDirtyCollection; this.hasDirtyCollection = hasDirtyCollection;
this.rowId = rowId; this.rowId = rowId;
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, id, previousState, session ); this.naturalIdMapping = persister.getNaturalIdMapping();
session.getPersistenceContextInternal().getNaturalIdHelper().manageLocalNaturalIdCrossReference( if ( naturalIdMapping == null ) {
persister, previousNaturalIdValues = null;
id, }
state, else {
previousNaturalIdValues, this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, id, previousState, session );
CachedNaturalIdValueSource.UPDATE session.getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
); id, state, persister,
CachedNaturalIdValueSource.UPDATE
);
}
} }
private Object[] determinePreviousNaturalIdValues( private static Object determinePreviousNaturalIdValues(
EntityPersister persister, EntityPersister persister,
Object id, Object[] previousState, Object id, Object[] previousState,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
if ( ! persister.hasNaturalIdentifier() ) {
return null;
}
final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( previousState != null ) { if ( previousState != null ) {
return persistenceContext.getNaturalIdHelper().extractNaturalIdValues( previousState, persister ); return persistenceContext.getNaturalIdHelper().extractNaturalIdValues( previousState, persister );
@ -116,50 +118,10 @@ public Object[] getPreviousState() {
return previousState; return previousState;
} }
public Object getPreviousVersion() {
return previousVersion;
}
public Object getNextVersion() {
return nextVersion;
}
public void setNextVersion(Object nextVersion) {
this.nextVersion = nextVersion;
}
public int[] getDirtyFields() {
return dirtyFields;
}
public boolean hasDirtyCollection() {
return hasDirtyCollection;
}
public Object getRowId() { public Object getRowId() {
return rowId; return rowId;
} }
public Object[] getPreviousNaturalIdValues() {
return previousNaturalIdValues;
}
protected Object getCacheEntry() {
return cacheEntry;
}
protected void setCacheEntry(Object cacheEntry) {
this.cacheEntry = cacheEntry;
}
protected SoftLock getLock() {
return lock;
}
protected void setLock(SoftLock lock) {
this.lock = lock;
}
@Override @Override
public void execute() throws HibernateException { public void execute() throws HibernateException {
final Object id = getId(); final Object id = getId();
@ -256,13 +218,15 @@ else if ( session.getCacheMode().isPutEnabled() ) {
} }
} }
session.getPersistenceContextInternal().getNaturalIdHelper().manageSharedNaturalIdCrossReference( if ( naturalIdMapping != null ) {
persister, session.getPersistenceContextInternal().getNaturalIdHelper().manageSharedResolution(
id, id,
state, naturalIdMapping.extractNaturalIdValues( state, session ),
previousNaturalIdValues, naturalIdMapping.extractNaturalIdValues( previousState, session ),
CachedNaturalIdValueSource.UPDATE persister,
); CachedNaturalIdValueSource.UPDATE
);
}
postUpdate(); postUpdate();

View File

@ -50,7 +50,7 @@ public static Object staticCreateEntityKey(Object id, EntityPersister persister,
return new CacheKeyImplementation( id, persister.getIdentifierType(), persister.getRootEntityName(), tenantIdentifier, factory ); return new CacheKeyImplementation( id, persister.getIdentifierType(), persister.getRootEntityName(), tenantIdentifier, factory );
} }
public static Object staticCreateNaturalIdKey(Object[] naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) { public static Object staticCreateNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
return new NaturalIdCacheKey( naturalIdValues, persister.getPropertyTypes(), persister.getNaturalIdentifierProperties(), persister.getRootEntityName(), session ); return new NaturalIdCacheKey( naturalIdValues, persister.getPropertyTypes(), persister.getNaturalIdentifierProperties(), persister.getRootEntityName(), session );
} }
@ -62,7 +62,7 @@ public static Object staticGetCollectionId(Object cacheKey) {
return ((CacheKeyImplementation) cacheKey).getId(); return ((CacheKeyImplementation) cacheKey).getId();
} }
public static Object[] staticGetNaturalIdValues(Object cacheKey) { public static Object staticGetNaturalIdValues(Object cacheKey) {
return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues(); return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues();
} }
@ -77,7 +77,7 @@ public Object createEntityKey(Object id, EntityPersister persister, SessionFacto
} }
@Override @Override
public Object createNaturalIdKey(Object[] naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) { public Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
return staticCreateNaturalIdKey(naturalIdValues, persister, session); return staticCreateNaturalIdKey(naturalIdValues, persister, session);
} }
@ -92,7 +92,7 @@ public Object getCollectionId(Object cacheKey) {
} }
@Override @Override
public Object[] getNaturalIdValues(Object cacheKey) { public Object getNaturalIdValues(Object cacheKey) {
return staticGetNaturalIdValues(cacheKey); return staticGetNaturalIdValues(cacheKey);
} }
} }

View File

@ -9,13 +9,12 @@
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ValueHolder; import org.hibernate.internal.util.ValueHolder;
import org.hibernate.type.EntityType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
@ -28,7 +27,7 @@
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class NaturalIdCacheKey implements Serializable { public class NaturalIdCacheKey implements Serializable {
private final Object[] naturalIdValues; private final Object naturalIdValues;
private final String entityName; private final String entityName;
private final String tenantId; private final String tenantId;
private final int hashCode; private final int hashCode;
@ -43,40 +42,20 @@ public class NaturalIdCacheKey implements Serializable {
* @param session The originating session * @param session The originating session
*/ */
public NaturalIdCacheKey( public NaturalIdCacheKey(
final Object[] naturalIdValues, final Object naturalIdValues,
Type[] propertyTypes, int[] naturalIdPropertyIndexes, final String entityName, Type[] propertyTypes,
int[] naturalIdPropertyIndexes,
final String entityName,
final SharedSessionContractImplementor session) { final SharedSessionContractImplementor session) {
this.entityName = entityName; this.entityName = entityName;
this.tenantId = session.getTenantIdentifier(); this.tenantId = session.getTenantIdentifier();
this.naturalIdValues = new Serializable[naturalIdValues.length]; final EntityMappingType entityMappingType = session.getFactory().getRuntimeMetamodels().getEntityMappingType( entityName );
final NaturalIdMapping naturalIdMapping = entityMappingType.getNaturalIdMapping();
final SessionFactoryImplementor factory = session.getFactory(); this.naturalIdValues = naturalIdMapping.disassemble( naturalIdValues, session );
this.hashCode = naturalIdMapping.calculateHashCode( naturalIdValues, session );
final int prime = 31;
int result = 1;
result = prime * result + ( ( this.entityName == null ) ? 0 : this.entityName.hashCode() );
result = prime * result + ( ( this.tenantId == null ) ? 0 : this.tenantId.hashCode() );
for ( int i = 0; i < naturalIdValues.length; i++ ) {
final int naturalIdPropertyIndex = naturalIdPropertyIndexes[i];
final Type type = propertyTypes[naturalIdPropertyIndex];
final Object value = naturalIdValues[i];
result = prime * result + (value != null ? type.getHashCode( value, factory ) : 0);
// The natural id may not be fully resolved in some situations. See HHH-7513 for one of them
// (re-attaching a mutable natural id uses a database snapshot and hydration does not resolve associations).
// TODO: The snapshot should probably be revisited at some point. Consider semi-resolving, hydrating, etc.
if (type instanceof EntityType && type.getSemiResolvedType( factory ).getReturnedClass().isInstance( value )) {
this.naturalIdValues[i] = value;
}
else {
this.naturalIdValues[i] = type.disassemble( value, session, null );
}
}
this.hashCode = result;
initTransients(); initTransients();
} }
@ -87,15 +66,20 @@ private void initTransients() {
public String initialize() { public String initialize() {
//Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys //Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys
//the only same way to differentiate the keys is to include the disassembled values in the string. //the only same way to differentiate the keys is to include the disassembled values in the string.
final StringBuilder toStringBuilder = new StringBuilder().append( entityName ).append( final StringBuilder toStringBuilder = new StringBuilder()
"##NaturalId[" ); .append( entityName ).append( "##NaturalId[" );
for ( int i = 0; i < naturalIdValues.length; i++ ) { if ( naturalIdValues instanceof Object[] ) {
toStringBuilder.append( naturalIdValues[i] ); final Object[] values = (Object[]) naturalIdValues;
if ( i + 1 < naturalIdValues.length ) { for ( int i = 0; i < values.length; i++ ) {
toStringBuilder.append( ", " ); toStringBuilder.append( values[ i ] );
if ( i + 1 < values.length ) {
toStringBuilder.append( ", " );
}
} }
} }
toStringBuilder.append( "]" ); else {
toStringBuilder.append( naturalIdValues );
}
return toStringBuilder.toString(); return toStringBuilder.toString();
} }
@ -114,7 +98,7 @@ public String getTenantId() {
} }
@SuppressWarnings( {"UnusedDeclaration"}) @SuppressWarnings( {"UnusedDeclaration"})
public Object[] getNaturalIdValues() { public Object getNaturalIdValues() {
return naturalIdValues; return naturalIdValues;
} }
@ -145,7 +129,7 @@ public boolean equals(Object o) {
final NaturalIdCacheKey other = (NaturalIdCacheKey) o; final NaturalIdCacheKey other = (NaturalIdCacheKey) o;
return Objects.equals( entityName, other.entityName ) return Objects.equals( entityName, other.entityName )
&& Objects.equals( tenantId, other.tenantId ) && Objects.equals( tenantId, other.tenantId )
&& Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues ); && Objects.deepEquals( this.naturalIdValues, other.naturalIdValues );
} }
private void readObject(ObjectInputStream ois) private void readObject(ObjectInputStream ois)

View File

@ -32,7 +32,7 @@ public Object createEntityKey(Object id, EntityPersister persister, SessionFacto
} }
@Override @Override
public Object createNaturalIdKey(Object[] naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) { public Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
// natural ids always need to be wrapped // natural ids always need to be wrapped
return new NaturalIdCacheKey(naturalIdValues, persister.getPropertyTypes(), persister.getNaturalIdentifierProperties(), null, session); return new NaturalIdCacheKey(naturalIdValues, persister.getPropertyTypes(), persister.getNaturalIdentifierProperties(), null, session);
} }
@ -48,7 +48,7 @@ public Object getCollectionId(Object cacheKey) {
} }
@Override @Override
public Object[] getNaturalIdValues(Object cacheKey) { public Object getNaturalIdValues(Object cacheKey) {
return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues(); return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues();
} }
} }

View File

@ -19,11 +19,11 @@ public interface CacheKeysFactory {
Object createEntityKey(Object id, EntityPersister persister, SessionFactoryImplementor factory, String tenantIdentifier); Object createEntityKey(Object id, EntityPersister persister, SessionFactoryImplementor factory, String tenantIdentifier);
Object createNaturalIdKey(Object[] naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session); Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session);
Object getEntityId(Object cacheKey); Object getEntityId(Object cacheKey);
Object getCollectionId(Object cacheKey); Object getCollectionId(Object cacheKey);
Object[] getNaturalIdValues(Object cacheKey); Object getNaturalIdValues(Object cacheKey);
} }

View File

@ -47,7 +47,7 @@ public interface NaturalIdDataAccess extends CachedDomainDataAccess {
* @return a key which can be used to identify an element unequivocally on this same region * @return a key which can be used to identify an element unequivocally on this same region
*/ */
Object generateCacheKey( Object generateCacheKey(
Object[] naturalIdValues, Object naturalIdValues,
EntityPersister rootEntityDescriptor, EntityPersister rootEntityDescriptor,
SharedSessionContractImplementor session); SharedSessionContractImplementor session);
@ -58,7 +58,7 @@ Object generateCacheKey(
* *
* @return the sequence of values which unequivocally identifies a cached element on this region * @return the sequence of values which unequivocally identifies a cached element on this region
*/ */
Object[] getNaturalIdValues(Object cacheKey); Object getNaturalIdValues(Object cacheKey);
/** /**
* Called afterQuery an item has been inserted (beforeQuery the transaction completes), * Called afterQuery an item has been inserted (beforeQuery the transaction completes),

View File

@ -31,14 +31,14 @@ public AbstractNaturalIdDataAccess(
@Override @Override
public Object generateCacheKey( public Object generateCacheKey(
Object[] naturalIdValues, Object naturalIdValues,
EntityPersister persister, EntityPersister persister,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
return keysFactory.createNaturalIdKey( naturalIdValues, persister, session ); return keysFactory.createNaturalIdKey( naturalIdValues, persister, session );
} }
@Override @Override
public Object[] getNaturalIdValues(Object cacheKey) { public Object getNaturalIdValues(Object cacheKey) {
return keysFactory.getNaturalIdValues( cacheKey ); return keysFactory.getNaturalIdValues( cacheKey );
} }

View File

@ -53,14 +53,14 @@ protected Comparator getVersionComparator() {
@Override @Override
public Object generateCacheKey( public Object generateCacheKey(
Object[] naturalIdValues, Object naturalIdValues,
EntityPersister rootEntityDescriptor, EntityPersister rootEntityDescriptor,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
return keysFactory.createNaturalIdKey( naturalIdValues, rootEntityDescriptor, session ); return keysFactory.createNaturalIdKey( naturalIdValues, rootEntityDescriptor, session );
} }
@Override @Override
public Object[] getNaturalIdValues(Object cacheKey) { public Object getNaturalIdValues(Object cacheKey) {
return keysFactory.getNaturalIdValues( cacheKey ); return keysFactory.getNaturalIdValues( cacheKey );
} }

View File

@ -408,11 +408,8 @@ public void setReadOnly(boolean readOnly, Object entity) {
} }
setStatus( Status.MANAGED ); setStatus( Status.MANAGED );
loadedState = getPersister().getPropertyValues( entity ); loadedState = getPersister().getPropertyValues( entity );
getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference( getPersistenceContext().getNaturalIdHelper().manageLocalResolution(
persister, id, loadedState, persister,
id,
loadedState,
null,
CachedNaturalIdValueSource.LOAD CachedNaturalIdValueSource.LOAD
); );
} }

View File

@ -8,7 +8,6 @@
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -19,10 +18,10 @@
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -70,12 +69,12 @@ protected SharedSessionContractImplementor session() {
* *
* @return {@code true} if a new entry was actually added; {@code false} otherwise. * @return {@code true} if a new entry was actually added; {@code false} otherwise.
*/ */
public boolean cacheNaturalIdCrossReference(EntityPersister persister, Object pk, Object[] naturalIdValues) { public boolean cacheResolution(EntityPersister persister, Object pk, Object naturalIdValues) {
validateNaturalId( persister, naturalIdValues ); validateNaturalId( persister, naturalIdValues );
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) { if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister ); entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister, persistenceContext );
NaturalIdResolutionCache previousInstance = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache ); NaturalIdResolutionCache previousInstance = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache );
if ( previousInstance != null ) { if ( previousInstance != null ) {
entityNaturalIdResolutionCache = previousInstance; entityNaturalIdResolutionCache = previousInstance;
@ -93,29 +92,29 @@ public boolean cacheNaturalIdCrossReference(EntityPersister persister, Object pk
* *
* @return The cached values, if any. May be different from incoming values. * @return The cached values, if any. May be different from incoming values.
*/ */
public Object[] removeNaturalIdCrossReference(EntityPersister persister, Object pk, Object[] naturalIdValues) { public Object removeResolutions(EntityPersister persister, Object pk, Object naturalIdValues) {
persister = locatePersisterForKey( persister ); persister = locatePersisterForKey( persister );
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
validateNaturalId( persister, naturalIdValues ); validateNaturalId( persister, naturalIdValues );
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
Object[] sessionCachedNaturalIdValues = null; Object sessionCachedNaturalIdValues = null;
if ( entityNaturalIdResolutionCache != null ) { if ( entityNaturalIdResolutionCache != null ) {
final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.remove( pk );
.remove( pk );
if ( cachedNaturalId != null ) { if ( cachedNaturalId != null ) {
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( cachedNaturalId ); entityNaturalIdResolutionCache.naturalIdToPkMap.remove( cachedNaturalId );
sessionCachedNaturalIdValues = cachedNaturalId.getValues(); sessionCachedNaturalIdValues = cachedNaturalId.getNaturalId();
} }
} }
if ( persister.hasNaturalIdCache() ) { if ( persister.hasNaturalIdCache() ) {
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() );
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey ); naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
if ( sessionCachedNaturalIdValues != null if ( sessionCachedNaturalIdValues != null
&& !Arrays.equals( sessionCachedNaturalIdValues, naturalIdValues ) ) { && ! naturalIdMapping.areEqual( sessionCachedNaturalIdValues, naturalIdValues, session() ) ) {
final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( sessionCachedNaturalIdValues, persister, session() ); final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( sessionCachedNaturalIdValues, persister, session() );
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey ); naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
} }
@ -133,7 +132,7 @@ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Object
* *
* @return {@code true} if the given naturalIdValues match the current cached values; {@code false} otherwise. * @return {@code true} if the given naturalIdValues match the current cached values; {@code false} otherwise.
*/ */
public boolean sameAsCached(EntityPersister persister, Object pk, Object[] naturalIdValues) { public boolean sameAsCached(EntityPersister persister, Object pk, Object naturalIdValues) {
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
return entityNaturalIdResolutionCache != null return entityNaturalIdResolutionCache != null
&& entityNaturalIdResolutionCache.sameAsCached( pk, naturalIdValues ); && entityNaturalIdResolutionCache.sameAsCached( pk, naturalIdValues );
@ -157,16 +156,17 @@ protected EntityPersister locatePersisterForKey(EntityPersister persister) {
* <li>the number of natural id values matches the expected number</li> * <li>the number of natural id values matches the expected number</li>
* </ul> * </ul>
* *
* @param persister The persister representing the entity type. * @param entityDescriptor The entity type descriptor
* @param naturalIdValues The natural id values * @param naturalIdValues The natural id values
*/ */
protected void validateNaturalId(EntityPersister persister, Object[] naturalIdValues) { protected void validateNaturalId(EntityPersister entityDescriptor, Object naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) { final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( naturalIdMapping == null ) {
throw new IllegalArgumentException( "Entity did not define a natural-id" ); throw new IllegalArgumentException( "Entity did not define a natural-id" );
} }
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." ); naturalIdMapping.validateInternalForm( naturalIdValues, persistenceContext.getSession() );
}
} }
/** /**
@ -177,7 +177,7 @@ protected void validateNaturalId(EntityPersister persister, Object[] naturalIdVa
* *
* @return The corresponding cross-referenced natural id values, or {@code null} if none * @return The corresponding cross-referenced natural id values, or {@code null} if none
*/ */
public Object[] findCachedNaturalId(EntityPersister persister, Object pk) { public Object findCachedNaturalId(EntityPersister persister, Object pk) {
persister = locatePersisterForKey( persister ); persister = locatePersisterForKey( persister );
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) { if ( entityNaturalIdResolutionCache == null ) {
@ -189,7 +189,7 @@ public Object[] findCachedNaturalId(EntityPersister persister, Object pk) {
return null; return null;
} }
return cachedNaturalId.getValues(); return cachedNaturalId.getNaturalId();
} }
/** /**
@ -199,19 +199,19 @@ public Object[] findCachedNaturalId(EntityPersister persister, Object pk) {
* *
* @param persister The persister representing the entity type. * @param persister The persister representing the entity type.
* @param naturalIdValues The natural id value(s) * @param naturalIdValues The natural id value(s)
* *
* @return The corresponding cross-referenced primary key, * @return The corresponding cross-referenced primary key,
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE}, * {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE},
* or {@code null} if none * or {@code null} if none
*/ */
public Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) { public Object findResolution(EntityPersister persister, Object naturalIdValues) {
persister = locatePersisterForKey( persister ); persister = locatePersisterForKey( persister );
validateNaturalId( persister, naturalIdValues ); validateNaturalId( persister, naturalIdValues );
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
Object pk; Object pk;
final CachedNaturalId cachedNaturalId = new CachedNaturalId( persister, naturalIdValues ); final CachedNaturalId cachedNaturalId = new CachedNaturalId( naturalIdValues, persister, persistenceContext );
if ( entityNaturalIdResolutionCache != null ) { if ( entityNaturalIdResolutionCache != null ) {
pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( cachedNaturalId ); pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( cachedNaturalId );
@ -221,7 +221,7 @@ public Object findCachedNaturalIdResolution(EntityPersister persister, Object[]
LOG.trace( LOG.trace(
"Resolved natural key -> primary key resolution in session cache: " + "Resolved natural key -> primary key resolution in session cache: " +
persister.getRootEntityName() + "#[" + persister.getRootEntityName() + "#[" +
Arrays.toString( naturalIdValues ) + "]" naturalIdValues + "]"
); );
} }
@ -264,14 +264,14 @@ public Object findCachedNaturalIdResolution(EntityPersister persister, Object[]
// protected to avoid Arrays.toString call unless needed // protected to avoid Arrays.toString call unless needed
LOG.tracef( LOG.tracef(
"Found natural key [%s] -> primary key [%s] xref in second-level cache for %s", "Found natural key [%s] -> primary key [%s] xref in second-level cache for %s",
Arrays.toString( naturalIdValues ), naturalIdValues,
pk, pk,
persister.getRootEntityName() persister.getRootEntityName()
); );
} }
if ( entityNaturalIdResolutionCache == null ) { if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister ); entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister, persistenceContext );
NaturalIdResolutionCache existingCache = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache ); NaturalIdResolutionCache existingCache = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache );
if ( existingCache != null ) { if ( existingCache != null ) {
entityNaturalIdResolutionCache = existingCache; entityNaturalIdResolutionCache = existingCache;
@ -329,7 +329,7 @@ public Collection<Object> getCachedPkResolutions(EntityPersister persister) {
* *
* @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled * @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled
*/ */
public void stashInvalidNaturalIdReference(EntityPersister persister, Object[] invalidNaturalIdValues) { public void stashInvalidNaturalIdReference(EntityPersister persister, Object invalidNaturalIdValues) {
persister = locatePersisterForKey( persister ); persister = locatePersisterForKey( persister );
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
@ -354,35 +354,27 @@ public void unStashInvalidNaturalIdReferences() {
* Used to put natural id values into collections. Useful mainly to apply equals/hashCode implementations. * Used to put natural id values into collections. Useful mainly to apply equals/hashCode implementations.
*/ */
private static class CachedNaturalId implements Serializable { private static class CachedNaturalId implements Serializable {
private final PersistenceContext persistenceContext;
private final EntityPersister persister; private final EntityPersister persister;
private final Object[] values; private final Object naturalId;
private final Type[] naturalIdTypes;
private int hashCode; private int hashCode;
public CachedNaturalId(EntityPersister persister, Object[] values) { public CachedNaturalId(Object naturalIdValue, EntityPersister persister, PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
this.persister = persister; this.persister = persister;
this.values = values; this.naturalId = naturalIdValue;
final int prime = 31; final int prime = 31;
int hashCodeCalculation = 1; int hashCodeCalculation = 1;
hashCodeCalculation = prime * hashCodeCalculation + persister.hashCode(); hashCodeCalculation = prime * hashCodeCalculation + persister.hashCode();
hashCodeCalculation = prime * hashCodeCalculation + persister.getNaturalIdMapping().calculateHashCode( naturalIdValue, persistenceContext.getSession());
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
naturalIdTypes = new Type[ naturalIdPropertyIndexes.length ];
int i = 0;
for ( int naturalIdPropertyIndex : naturalIdPropertyIndexes ) {
final Type type = persister.getPropertyType( persister.getPropertyNames()[ naturalIdPropertyIndex ] );
naturalIdTypes[i] = type;
final int elementHashCode = values[i] == null ? 0 :type.getHashCode( values[i], persister.getFactory() );
hashCodeCalculation = prime * hashCodeCalculation + elementHashCode;
i++;
}
this.hashCode = hashCodeCalculation; this.hashCode = hashCodeCalculation;
} }
public Object[] getValues() { public Object getNaturalId() {
return values; return naturalId;
} }
@Override @Override
@ -403,17 +395,11 @@ public boolean equals(Object obj) {
} }
final CachedNaturalId other = (CachedNaturalId) obj; final CachedNaturalId other = (CachedNaturalId) obj;
return persister.equals( other.persister ) && isSame( other.values ); return persister.equals( other.persister ) && isSame( other.naturalId );
} }
private boolean isSame(Object[] otherValues) { private boolean isSame(Object otherValue) {
// lengths have already been verified at this point return persister.getNaturalIdMapping().areEqual( naturalId, otherValue, persistenceContext.getSession() );
for ( int i = 0; i < naturalIdTypes.length; i++ ) {
if ( ! naturalIdTypes[i].isEqual( values[i], otherValues[i], persister.getFactory() ) ) {
return false;
}
}
return true;
} }
} }
@ -421,6 +407,8 @@ private boolean isSame(Object[] otherValues) {
* Represents the persister-specific cross-reference cache. * Represents the persister-specific cross-reference cache.
*/ */
private static class NaturalIdResolutionCache implements Serializable { private static class NaturalIdResolutionCache implements Serializable {
private final PersistenceContext persistenceContext;
private final EntityPersister persister; private final EntityPersister persister;
private Map<Object, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<>(); private Map<Object, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<>();
@ -428,15 +416,16 @@ private static class NaturalIdResolutionCache implements Serializable {
private List<CachedNaturalId> invalidNaturalIdList; private List<CachedNaturalId> invalidNaturalIdList;
private NaturalIdResolutionCache(EntityPersister persister) { private NaturalIdResolutionCache(EntityPersister persister, PersistenceContext persistenceContext) {
this.persister = persister; this.persister = persister;
this.persistenceContext = persistenceContext;
} }
public EntityPersister getPersister() { public EntityPersister getPersister() {
return persister; return persister;
} }
public boolean sameAsCached(Object pk, Object[] naturalIdValues) { public boolean sameAsCached(Object pk, Object naturalIdValues) {
if ( pk == null ) { if ( pk == null ) {
return false; return false;
} }
@ -449,7 +438,7 @@ public boolean sameAsCached(Object pk, Object[] naturalIdValues) {
return false; return false;
} }
public boolean cache(Object pk, Object[] naturalIdValues) { public boolean cache(Object pk, Object naturalIdValues) {
if ( pk == null ) { if ( pk == null ) {
return false; return false;
} }
@ -461,23 +450,23 @@ public boolean cache(Object pk, Object[] naturalIdValues) {
naturalIdToPkMap.remove( initial ); naturalIdToPkMap.remove( initial );
} }
final CachedNaturalId cachedNaturalId = new CachedNaturalId( persister, naturalIdValues ); final CachedNaturalId cachedNaturalId = new CachedNaturalId( naturalIdValues, persister, persistenceContext );
pkToNaturalIdMap.put( pk, cachedNaturalId ); pkToNaturalIdMap.put( pk, cachedNaturalId );
naturalIdToPkMap.put( cachedNaturalId, pk ); naturalIdToPkMap.put( cachedNaturalId, pk );
return true; return true;
} }
public void stashInvalidNaturalIdReference(Object[] invalidNaturalIdValues) { public void stashInvalidNaturalIdReference(Object invalidNaturalIdValues) {
if ( invalidNaturalIdList == null ) { if ( invalidNaturalIdList == null ) {
invalidNaturalIdList = new ArrayList<>(); invalidNaturalIdList = new ArrayList<>();
} }
invalidNaturalIdList.add( new CachedNaturalId( persister, invalidNaturalIdValues ) ); invalidNaturalIdList.add( new CachedNaturalId( invalidNaturalIdValues, persister, persistenceContext ) );
} }
public boolean containsInvalidNaturalIdReference(Object[] naturalIdValues) { public boolean containsInvalidNaturalIdReference(Object naturalIdValues) {
return invalidNaturalIdList != null return invalidNaturalIdList != null
&& invalidNaturalIdList.contains( new CachedNaturalId( persister, naturalIdValues ) ); && invalidNaturalIdList.contains( new CachedNaturalId( naturalIdValues, persister, persistenceContext ) );
} }
public void unStashInvalidNaturalIdReferences() { public void unStashInvalidNaturalIdReferences() {

View File

@ -61,6 +61,7 @@
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap; import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
import org.hibernate.internal.util.collections.IdentityMap; import org.hibernate.internal.util.collections.IdentityMap;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -328,7 +329,7 @@ public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws
} }
@Override @Override
public Object[] getNaturalIdSnapshot(Object id, EntityPersister persister) throws HibernateException { public Object getNaturalIdSnapshot(Object id, EntityPersister persister) throws HibernateException {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
return null; return null;
} }
@ -336,7 +337,7 @@ public Object[] getNaturalIdSnapshot(Object id, EntityPersister persister) throw
persister = locateProperPersister( persister ); persister = locateProperPersister( persister );
// let's first see if it is part of the natural id cache... // let's first see if it is part of the natural id cache...
final Object[] cachedValue = naturalIdHelper.findCachedNaturalId( persister, id ); final Object cachedValue = naturalIdHelper.findCachedNaturalId( persister, id );
if ( cachedValue != null ) { if ( cachedValue != null ) {
return cachedValue; return cachedValue;
} }
@ -345,7 +346,7 @@ public Object[] getNaturalIdSnapshot(Object id, EntityPersister persister) throw
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) { if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// an immutable natural-id is not retrieved during a normal database-snapshot operation... // an immutable natural-id is not retrieved during a normal database-snapshot operation...
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session ); final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad( naturalIdHelper.cacheResolutionFromLoad(
persister, persister,
id, id,
dbValue dbValue
@ -365,7 +366,7 @@ public Object[] getNaturalIdSnapshot(Object id, EntityPersister persister) throw
for ( int i = 0; i < props.length; i++ ) { for ( int i = 0; i < props.length; i++ ) {
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ]; naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
} }
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad( naturalIdHelper.cacheResolutionFromLoad(
persister, persister,
id, id,
naturalIdSnapshotSubSet naturalIdSnapshotSubSet
@ -1947,10 +1948,10 @@ private NaturalIdXrefDelegate getNaturalIdXrefDelegate() {
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() { private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
@Override @Override
public void cacheNaturalIdCrossReferenceFromLoad( public void cacheResolutionFromLoad(
EntityPersister persister, EntityPersister persister,
Object id, Object id,
Object[] naturalIdValues) { Object naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
// nothing to do // nothing to do
return; return;
@ -1962,19 +1963,18 @@ public void cacheNaturalIdCrossReferenceFromLoad(
// from a single load event. The first put journal would come from the natural id resolution; // 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 // the second comes from the entity loading. In this condition, we want to avoid the multiple
// 'put' stats incrementing. // 'put' stats incrementing.
final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues ); final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalIdValues );
if ( justAddedLocally && persister.hasNaturalIdCache() ) { if ( justAddedLocally && persister.hasNaturalIdCache() ) {
managedSharedCacheEntries( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD ); managedSharedResolutions( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD );
} }
} }
@Override @Override
public void manageLocalNaturalIdCrossReference( public void manageLocalResolution(
EntityPersister persister,
Object id, Object id,
Object[] state, Object naturalId,
Object[] previousState, EntityPersister persister,
CachedNaturalIdValueSource source) { CachedNaturalIdValueSource source) {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
// nothing to do // nothing to do
@ -1982,18 +1982,17 @@ public void manageLocalNaturalIdCrossReference(
} }
persister = locateProperPersister( persister ); persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
// cache // cache
getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues ); getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalId );
} }
@Override @Override
public void manageSharedNaturalIdCrossReference( public void manageSharedResolution(
EntityPersister persister,
final Object id, final Object id,
Object[] state, Object naturalId,
Object[] previousState, Object previousNaturalId,
EntityPersister persister,
CachedNaturalIdValueSource source) { CachedNaturalIdValueSource source) {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
// nothing to do // nothing to do
@ -2006,17 +2005,17 @@ public void manageSharedNaturalIdCrossReference(
} }
persister = locateProperPersister( persister ); persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister ); final Object naturalIdValues = extractNaturalIdValues( naturalId, persister );
final Object[] previousNaturalIdValues = previousState == null ? null : extractNaturalIdValues( previousState, persister ); final Object previousNaturalIdValues = previousNaturalId == null ? null : extractNaturalIdValues( previousNaturalId, persister );
managedSharedCacheEntries( persister, id, naturalIdValues, previousNaturalIdValues, source ); managedSharedResolutions( persister, id, naturalIdValues, previousNaturalIdValues, source );
} }
private void managedSharedCacheEntries( private void managedSharedResolutions(
EntityPersister persister, EntityPersister persister,
final Object id, final Object id,
Object[] naturalIdValues, Object naturalIdValues,
Object[] previousNaturalIdValues, Object previousNaturalIdValues,
CachedNaturalIdValueSource source) { CachedNaturalIdValueSource source) {
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session ); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session );
@ -2133,26 +2132,25 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI
} }
@Override @Override
public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Object id, Object[] state) { public Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId) {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
// nothing to do // nothing to do
return null; return null;
} }
persister = locateProperPersister( persister ); persister = locateProperPersister( persister );
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference( final Object localNaturalIdValues = getNaturalIdXrefDelegate().removeResolutions(
persister, persister,
id, id,
naturalIdValues naturalId
); );
return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues; return localNaturalIdValues != null ? localNaturalIdValues : naturalId;
} }
@Override @Override
public void removeSharedNaturalIdCrossReference(EntityPersister persister, Object id, Object[] naturalIdValues) { public void removeSharedResolution(EntityPersister persister, Object id, Object naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) { if ( !persister.hasNaturalIdentifier() ) {
// nothing to do // nothing to do
return; return;
@ -2180,46 +2178,37 @@ public void removeSharedNaturalIdCrossReference(EntityPersister persister, Objec
} }
@Override @Override
public Object[] findCachedNaturalId(EntityPersister persister, Object pk) { public Object findCachedNaturalId(EntityPersister persister, Object pk) {
return getNaturalIdXrefDelegate().findCachedNaturalId( locateProperPersister( persister ), pk ); return getNaturalIdXrefDelegate().findCachedNaturalId( locateProperPersister( persister ), pk );
} }
@Override @Override
public Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) { public Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
return getNaturalIdXrefDelegate().findCachedNaturalIdResolution( locateProperPersister( persister ), naturalIdValues ); return getNaturalIdXrefDelegate().findResolution( locateProperPersister( persister ), naturalIdValues );
} }
@Override @Override
public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister) { public Object extractNaturalIdValues(Object[] state, EntityPersister persister) {
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties(); final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
if ( state.length == naturalIdPropertyIndexes.length ) { assert naturalIdMapping != null;
return state;
}
final Object[] naturalIdValues = new Object[naturalIdPropertyIndexes.length]; return naturalIdMapping.extractNaturalIdValues( state, getSession() );
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
naturalIdValues[i] = state[naturalIdPropertyIndexes[i]];
}
return naturalIdValues;
} }
@Override @Override
public Object[] extractNaturalIdValues(Object entity, EntityPersister persister) { public Object extractNaturalIdValues(Object entity, EntityPersister persister) {
if ( entity == null ) { if ( entity == null ) {
throw new AssertionFailure( "Entity from which to extract natural id value(s) cannot be null" ); throw new AssertionFailure( "Entity from which to extract natural id value(s) cannot be null" );
} }
if ( persister == null ) { if ( persister == null ) {
throw new AssertionFailure( "Persister to use in extracting natural id value(s) cannot be null" ); throw new AssertionFailure( "Persister to use in extracting natural id value(s) cannot be null" );
} }
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties(); final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
final Object[] naturalIdValues = new Object[naturalIdentifierProperties.length]; assert naturalIdMapping != null;
for ( int i = 0; i < naturalIdentifierProperties.length; i++ ) { return naturalIdMapping.extractNaturalIdValues( entity, session );
naturalIdValues[i] = persister.getPropertyValue( entity, naturalIdentifierProperties[i] );
}
return naturalIdValues;
} }
@Override @Override
@ -2236,7 +2225,7 @@ public void handleSynchronization(EntityPersister persister, Object pk, Object e
persister = locateProperPersister( persister ); persister = locateProperPersister( persister );
final Object[] naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister ); final Object naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister );
final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate(); final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate();
final boolean changed = ! naturalIdXrefDelegate.sameAsCached( final boolean changed = ! naturalIdXrefDelegate.sameAsCached(
persister, persister,
@ -2245,11 +2234,11 @@ public void handleSynchronization(EntityPersister persister, Object pk, Object e
); );
if ( changed ) { if ( changed ) {
final Object[] cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk ); final Object cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, pk, naturalIdValuesFromCurrentObjectState ); naturalIdXrefDelegate.cacheResolution( persister, pk, naturalIdValuesFromCurrentObjectState );
naturalIdXrefDelegate.stashInvalidNaturalIdReference( persister, cachedNaturalIdValues ); naturalIdXrefDelegate.stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
removeSharedNaturalIdCrossReference( removeSharedResolution(
persister, persister,
pk, pk,
cachedNaturalIdValues cachedNaturalIdValues
@ -2264,7 +2253,7 @@ public void cleanupFromSynchronizations() {
@Override @Override
public void handleEviction(Object object, EntityPersister persister, Serializable identifier) { public void handleEviction(Object object, EntityPersister persister, Serializable identifier) {
getNaturalIdXrefDelegate().removeNaturalIdCrossReference( getNaturalIdXrefDelegate().removeResolutions(
persister, persister,
identifier, identifier,
findCachedNaturalId( persister, identifier ) findCachedNaturalId( persister, identifier )
@ -2276,15 +2265,4 @@ public void handleEviction(Object object, EntityPersister persister, Serializabl
public NaturalIdHelper getNaturalIdHelper() { public NaturalIdHelper getNaturalIdHelper() {
return naturalIdHelper; return naturalIdHelper;
} }
private Object[] getNaturalIdValues(Object[] state, EntityPersister persister) {
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
final Object[] naturalIdValues = new Object[naturalIdPropertyIndexes.length];
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
naturalIdValues[i] = state[naturalIdPropertyIndexes[i]];
}
return naturalIdValues;
}
} }

View File

@ -335,7 +335,7 @@ public static void initializeEntityFromEntityEntryLoadedState(
} }
if ( persister.hasNaturalIdentifier() ) { if ( persister.hasNaturalIdentifier() ) {
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
persister, persister,
id, id,
persistenceContext.getNaturalIdHelper().extractNaturalIdValues( hydratedState, persister ) persistenceContext.getNaturalIdHelper().extractNaturalIdValues( hydratedState, persister )

View File

@ -147,7 +147,7 @@ public interface PersistenceContext {
* *
* @return The current (non-cached) snapshot of the entity's natural id state. * @return The current (non-cached) snapshot of the entity's natural id state.
*/ */
Object[] getNaturalIdSnapshot(Object id, EntityPersister persister); Object getNaturalIdSnapshot(Object id, EntityPersister persister);
/** /**
* Add a canonical mapping from entity key to entity instance * Add a canonical mapping from entity key to entity instance
@ -814,7 +814,7 @@ interface NaturalIdHelper {
* *
* @return The extracted natural id values * @return The extracted natural id values
*/ */
Object[] extractNaturalIdValues(Object[] state, EntityPersister persister); Object extractNaturalIdValues(Object[] state, EntityPersister persister);
/** /**
* Given an entity instance, extract the values that represent the natural id * Given an entity instance, extract the values that represent the natural id
@ -824,35 +824,27 @@ interface NaturalIdHelper {
* *
* @return The extracted natural id values * @return The extracted natural id values
*/ */
Object[] extractNaturalIdValues(Object entity, EntityPersister persister); Object extractNaturalIdValues(Object entity, EntityPersister persister);
/** /**
* Performs processing related to creating natural-id cross-reference entries on load. * Performs processing related to creating natural-id cross-reference entries on load.
* Handles both the local (transactional) and shared (second-level) caches. * Handles both the local (transactional) and shared (second-level) caches.
* @param persister The persister representing the entity type. * @param persister The persister representing the entity type.
* @param id The primary key value * @param id The primary key value
* @param naturalIdValues The natural id values * @param naturalIdValues The natural id values
*/ */
void cacheNaturalIdCrossReferenceFromLoad( void cacheResolutionFromLoad(
EntityPersister persister, EntityPersister persister,
Object id, Object id,
Object[] naturalIdValues); Object naturalIdValues);
/** /**
* Creates necessary local cross-reference entries. * Creates necessary local cross-reference entries.
*
* @param persister The persister representing the entity type.
* @param id The primary key value
* @param state Generally the "full entity state array", though could also be the natural id values array
* @param previousState Generally the "full entity state array", though could also be the natural id values array.
* Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE}
* @param source Enumeration representing how these values are coming into cache.
*/ */
void manageLocalNaturalIdCrossReference( void manageLocalResolution(
EntityPersister persister,
Object id, Object id,
Object[] state, Object naturalId,
Object[] previousState, EntityPersister persister,
CachedNaturalIdValueSource source); CachedNaturalIdValueSource source);
/** /**
@ -860,37 +852,29 @@ void manageLocalNaturalIdCrossReference(
* *
* @param persister The persister representing the entity type. * @param persister The persister representing the entity type.
* @param id The primary key value * @param id The primary key value
* @param state Generally the "full entity state array", though could also be the natural id values array * @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). * @return The local cached natural id values (could be different from given values).
*/ */
Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Object id, Object[] state); Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId);
/** /**
* Creates necessary shared (second level cache) cross-reference entries. * Creates necessary shared (second level cache) cross-reference entries.
*
* @param persister The persister representing the entity type.
* @param id The primary key value
* @param state Generally the "full entity state array", though could also be the natural id values array
* @param previousState Generally the "full entity state array", though could also be the natural id values array.
* Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE}
* @param source Enumeration representing how these values are coming into cache.
*/ */
void manageSharedNaturalIdCrossReference( void manageSharedResolution(
EntityPersister persister,
Object id, Object id,
Object[] state, Object naturalId,
Object[] previousState, Object previousNaturalId,
EntityPersister persister,
CachedNaturalIdValueSource source); CachedNaturalIdValueSource source);
/** /**
* Cleans up local cross-reference entries. * Cleans up local cross-reference entries.
* * @param persister The persister representing the entity type.
* @param persister The persister representing the entity type.
* @param id The primary key value * @param id The primary key value
* @param naturalIdValues The natural id values array * @param naturalIdValues The natural id values array
*/ */
void removeSharedNaturalIdCrossReference(EntityPersister persister, Object id, Object[] naturalIdValues); void removeSharedResolution(EntityPersister persister, Object id, Object naturalIdValues);
/** /**
* Given a persister and primary key, find the corresponding cross-referenced natural id values. * Given a persister and primary key, find the corresponding cross-referenced natural id values.
@ -900,7 +884,7 @@ void manageSharedNaturalIdCrossReference(
* *
* @return The cross-referenced natural-id values, or {@code null} * @return The cross-referenced natural-id values, or {@code null}
*/ */
Object[] findCachedNaturalId(EntityPersister persister, Object pk); Object findCachedNaturalId(EntityPersister persister, Object pk);
/** /**
* Given a persister and natural-id values, find the corresponding cross-referenced primary key. Will return * Given a persister and natural-id values, find the corresponding cross-referenced primary key. Will return

View File

@ -22,7 +22,6 @@
import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.PersistentAttributeInterceptor;
@ -38,9 +37,9 @@
import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
import org.hibernate.metadata.ClassMetadata; import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper; import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -103,48 +102,17 @@ private void checkNaturalId(
} }
} }
if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) { final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
if ( !persister.getEntityMetamodel().hasImmutableNaturalId() ) { if ( naturalIdMapping == null ) {
// EARLY EXIT!!! return;
// the natural id is mutable (!immutable), no need to do the below checks
return;
}
final int[] naturalIdentifierPropertiesIndexes = persister.getNaturalIdentifierProperties();
final Type[] propertyTypes = persister.getPropertyTypes();
final boolean[] propertyUpdateability = persister.getPropertyUpdateability();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final Object[] snapshot = loaded == null
? persistenceContext.getNaturalIdSnapshot( entry.getId(), persister )
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loaded, persister );
for ( int i = 0; i < naturalIdentifierPropertiesIndexes.length; i++ ) {
final int naturalIdentifierPropertyIndex = naturalIdentifierPropertiesIndexes[i];
if ( propertyUpdateability[naturalIdentifierPropertyIndex] ) {
// if the given natural id property is updatable (mutable), there is nothing to check
continue;
}
final Type propertyType = propertyTypes[naturalIdentifierPropertyIndex];
if ( !propertyType.isEqual( current[naturalIdentifierPropertyIndex], snapshot[i] ) ) {
throw new HibernateException(
String.format(
"An immutable natural identifier of entity %s was altered from `%s` to `%s`",
persister.getEntityName(),
propertyTypes[naturalIdentifierPropertyIndex].toLoggableString(
snapshot[i],
session.getFactory()
),
propertyTypes[naturalIdentifierPropertyIndex].toLoggableString(
current[naturalIdentifierPropertyIndex],
session.getFactory()
)
)
);
}
}
} }
if ( entry.getStatus() == Status.READ_ONLY ) {
// nothing to check
return;
}
naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session );
} }
/** /**

View File

@ -538,7 +538,7 @@ private Object doLoad(
if ( entity != null && persister.hasNaturalIdentifier() ) { if ( entity != null && persister.hasNaturalIdentifier() ) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper(); final PersistenceContext.NaturalIdHelper naturalIdHelper = persistenceContext.getNaturalIdHelper();
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad( naturalIdHelper.cacheResolutionFromLoad(
persister, persister,
event.getEntityId(), event.getEntityId(),
naturalIdHelper.extractNaturalIdValues( naturalIdHelper.extractNaturalIdValues(

View File

@ -136,7 +136,7 @@ protected Object loadFromDatasource(final ResolveNaturalIdEvent event) {
//PK can be null if the entity doesn't exist //PK can be null if the entity doesn't exist
if (pk != null) { if (pk != null) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
event.getEntityPersister(), event.getEntityPersister(),
pk, pk,
event.getOrderedNaturalIdValues() event.getOrderedNaturalIdValues()

View File

@ -75,7 +75,7 @@ public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options,
(naturalId, session1) -> { (naturalId, session1) -> {
// `naturalId` here is the one passed in by the API as part of the values array // `naturalId` here is the one passed in by the API as part of the values array
// todo (6.0) : use this to help create the ordered results // todo (6.0) : use this to help create the ordered results
return entityDescriptor.getNaturalIdMapping().normalizeValue( naturalId, session ); return entityDescriptor.getNaturalIdMapping().normalizeIncomingValue( naturalId, session );
}, },
session.getLoadQueryInfluencers(), session.getLoadQueryInfluencers(),
lockOptions, lockOptions,

View File

@ -47,7 +47,7 @@ interface KeyValueResolver {
* the "true" form - single value for simple natural-ids and an array for * the "true" form - single value for simple natural-ids and an array for
* compound natural-ids. * compound natural-ids.
* *
* Generally delegates to {@link org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeValue} * Generally delegates to {@link org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeIncomingValue}
*/ */
Object resolveKeyToLoad(Object incoming, SharedSessionContractImplementor session); Object resolveKeyToLoad(Object incoming, SharedSessionContractImplementor session);
} }

View File

@ -61,7 +61,7 @@ protected void applyNaturalIdAsJdbcParameters(
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
assert jdbcParameters.size() == 1; assert jdbcParameters.size() == 1;
final Object bindableValue = naturalIdMapping().normalizeValue( naturalIdToLoad, session ); final Object bindableValue = naturalIdMapping().normalizeIncomingValue( naturalIdToLoad, session );
final SingularAttributeMapping attributeMapping = naturalIdMapping().getNaturalIdAttributes().get( 0 ); final SingularAttributeMapping attributeMapping = naturalIdMapping().getNaturalIdAttributes().get( 0 );
jdbcParamBindings.registerParametersForEachJdbcValue( jdbcParamBindings.registerParametersForEachJdbcValue(
@ -75,7 +75,7 @@ protected void applyNaturalIdAsJdbcParameters(
@Override @Override
protected Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session) { protected Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session) {
return naturalIdMapping().normalizeValue( naturalIdToLoad, session ); return naturalIdMapping().normalizeIncomingValue( naturalIdToLoad, session );
} }
@Override @Override
@ -161,7 +161,7 @@ protected boolean isSimple() {
public Object resolveNaturalIdToId( public Object resolveNaturalIdToId(
Object naturalIdValue, Object naturalIdValue,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
final Object bindValue = naturalIdMapping().normalizeValue( naturalIdValue, session ); final Object bindValue = naturalIdMapping().normalizeIncomingValue( naturalIdValue, session );
final SessionFactoryImplementor sessionFactory = session.getFactory(); final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final JdbcServices jdbcServices = sessionFactory.getJdbcServices();

View File

@ -6,9 +6,11 @@
*/ */
package org.hibernate.metamodel.mapping; package org.hibernate.metamodel.mapping;
import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
@ -103,4 +105,9 @@ default int forEachSelection(int offset, SelectionConsumer consumer) {
} }
EntityMappingType findContainingEntityMapping(); EntityMappingType findContainingEntityMapping();
default boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) {
// NOTE : deepEquals to account for arrays (compound natural-id)
return Objects.deepEquals( one, other );
}
} }

View File

@ -8,6 +8,7 @@
import java.util.List; import java.util.List;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader; import org.hibernate.loader.ast.spi.NaturalIdLoader;
@ -23,6 +24,21 @@ public interface NaturalIdMapping extends VirtualModelPart {
*/ */
List<SingularAttributeMapping> getNaturalIdAttributes(); List<SingularAttributeMapping> getNaturalIdAttributes();
/**
* Whether the natural-id is immutable. This is the same as saying that none of
* the attributes are mutable
*/
boolean isImmutable();
/**
* Verify the natural-id value(s) we are about to flush to the database
*/
void verifyFlushState(
Object id,
Object[] currentState,
Object[] loadedState,
SharedSessionContractImplementor session);
@Override @Override
default String getPartName() { default String getPartName() {
return PART_NAME; return PART_NAME;
@ -31,5 +47,40 @@ default String getPartName() {
NaturalIdLoader getNaturalIdLoader(); NaturalIdLoader getNaturalIdLoader();
MultiNaturalIdLoader getMultiNaturalIdLoader(); MultiNaturalIdLoader getMultiNaturalIdLoader();
Object normalizeValue(Object incoming, SharedSessionContractImplementor session); /**
* Given an array of "full entity state", extract the normalized natural id representation
*
* @param state The attribute state array
*
* @return The extracted natural id values. This is a normalized
*/
Object extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session);
/**
* Given an entity instance, extract the normalized natural id representation
*
* @param entity The entity instance
*
* @return The extracted natural id values
*/
Object extractNaturalIdValues(Object entity, SharedSessionContractImplementor session);
/**
* Normalize an incoming (user supplied) natural-id value.
*/
Object normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session);
/**
* Validates a natural id value(s) for the described natural-id based on the expected internal representation
*/
void validateInternalForm(Object naturalIdValue, SharedSessionContractImplementor session);
/**
* Calculate the hash-code of a natural-id value
*
* @param value The natural-id value
*
* @return The hash-code
*/
int calculateHashCode(Object value, SharedSessionContractImplementor session);
} }

View File

@ -31,6 +31,10 @@ public NavigableRole getNavigableRole() {
return role; return role;
} }
public EntityMappingType getDeclaringType() {
return declaringType;
}
@Override @Override
public EntityMappingType findContainingEntityMapping() { public EntityMappingType findContainingEntityMapping() {
return declaringType; return declaringType;

View File

@ -7,11 +7,14 @@
package org.hibernate.metamodel.mapping.internal; package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener; import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener; import org.hibernate.loader.NaturalIdPreLoadListener;
@ -25,6 +28,7 @@
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
@ -42,6 +46,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
private final List<SingularAttributeMapping> attributes; private final List<SingularAttributeMapping> attributes;
private final List<JdbcMapping> jdbcMappings; private final List<JdbcMapping> jdbcMappings;
private final boolean immutable;
private final NaturalIdLoader<?> loader; private final NaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader; private final MultiNaturalIdLoader<?> multiLoader;
@ -54,11 +59,15 @@ public CompoundNaturalIdMapping(
super( declaringType, cacheRegionName ); super( declaringType, cacheRegionName );
this.attributes = attributes; this.attributes = attributes;
boolean anyMutable = false;
final List<JdbcMapping> jdbcMappings = new ArrayList<>(); final List<JdbcMapping> jdbcMappings = new ArrayList<>();
for ( int i = 0; i < attributes.size(); i++ ) { for ( int i = 0; i < attributes.size(); i++ ) {
attributes.get( i ).forEachJdbcType( (index, jdbcMapping) -> jdbcMappings.add( jdbcMapping ) ); final SingularAttributeMapping attributeMapping = attributes.get( i );
attributeMapping.forEachJdbcType( (index, jdbcMapping) -> jdbcMappings.add( jdbcMapping ) );
anyMutable = anyMutable || attributeMapping.getAttributeMetadataAccess().resolveAttributeMetadata( null ).isUpdatable();
} }
this.jdbcMappings = jdbcMappings; this.jdbcMappings = jdbcMappings;
this.immutable = ! anyMutable;
loader = new CompoundNaturalIdLoader<>( loader = new CompoundNaturalIdLoader<>(
this, this,
@ -70,11 +79,43 @@ public CompoundNaturalIdMapping(
multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess ); multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess );
} }
@Override
public Object[] extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session) {
if ( state == null ) {
return null;
}
if ( state.length == attributes.size() ) {
return state;
}
final Object[] values = new Object[ attributes.size() ];
for ( int i = 0; i <= attributes.size() - 1; i++ ) {
final SingularAttributeMapping attributeMapping = attributes.get( i );
final Object domainValue = state[ attributeMapping.getStateArrayPosition() ];
values[ i ] = attributeMapping.disassemble( domainValue, session );
}
return values;
}
@Override
public Object[] extractNaturalIdValues(Object entity, SharedSessionContractImplementor session) {
final Object[] values = new Object[ attributes.size() ];
for ( int i = 0; i < attributes.size(); i++ ) {
values[i] = attributes.get( i ).getPropertyAccess().getGetter().get( entity );
}
return values;
}
@Override @Override
@SuppressWarnings( "rawtypes" ) @SuppressWarnings( "rawtypes" )
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) { public Object[] normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session) {
if ( incoming instanceof Object[] ) { if ( incoming instanceof Object[] ) {
return incoming; return (Object[]) incoming;
} }
if ( incoming instanceof Map ) { if ( incoming instanceof Map ) {
@ -90,11 +131,95 @@ public Object normalizeValue(Object incoming, SharedSessionContractImplementor s
throw new UnsupportedOperationException( "Do not know how to normalize compound natural-id value : " + incoming ); throw new UnsupportedOperationException( "Do not know how to normalize compound natural-id value : " + incoming );
} }
@Override
public void validateInternalForm(Object naturalIdValue, SharedSessionContractImplementor session) {
if ( naturalIdValue == null ) {
return;
}
// should be an array, with a size equal to the number of attributes making up this compound natural-id
if ( naturalIdValue instanceof Object[] ) {
final Object[] values = (Object[]) naturalIdValue;
if ( values.length != attributes.size() ) {
throw new IllegalArgumentException(
"Natural-id value [" + naturalIdValue + "] did not contain the expected number of elements ["
+ attributes.size() + "]"
);
}
return;
}
throw new IllegalArgumentException( "Natural-id value [" + naturalIdValue + "] was not an array as expected" );
}
@Override
public int calculateHashCode(Object value, SharedSessionContractImplementor session) {
return 0;
}
@Override
public void verifyFlushState(Object id, Object[] currentState, Object[] loadedState, SharedSessionContractImplementor session) {
if ( ! immutable ) {
// EARLY EXIT!!!
// the natural id is mutable (!immutable), no need to do the checks
return;
}
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityPersister persister = getDeclaringType().getEntityPersister();
final Object[] naturalId = extractNaturalIdValues( currentState, session );
final Object snapshot = loadedState == null
? persistenceContext.getNaturalIdSnapshot( id, persister )
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
final Object[] previousNaturalId = (Object[]) snapshot;
assert naturalId.length == getNaturalIdAttributes().size();
assert previousNaturalId.length == naturalId.length;
for ( int i = 0; i < getNaturalIdAttributes().size(); i++ ) {
final SingularAttributeMapping attributeMapping = getNaturalIdAttributes().get( i );
final boolean updatable = attributeMapping.getAttributeMetadataAccess().resolveAttributeMetadata( persister ).isUpdatable();
if ( updatable ) {
// property is updatable (mutable), there is nothing to check
continue;
}
final Object currentValue = naturalId[ i ];
final Object previousValue = previousNaturalId[ i ];
if ( ! attributeMapping.areEqual( currentValue, previousValue, session ) ) {
throw new HibernateException(
String.format(
"An immutable attribute [%s] within compound natural identifier of entity %s was altered from `%s` to `%s`",
attributeMapping.getAttributeName(),
persister.getEntityName(),
previousValue,
currentValue
)
);
}
}
}
@Override
public boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) {
return Arrays.equals( (Object[]) one, (Object[]) other );
}
@Override @Override
public List<SingularAttributeMapping> getNaturalIdAttributes() { public List<SingularAttributeMapping> getNaturalIdAttributes() {
return attributes; return attributes;
} }
@Override
public boolean isImmutable() {
return immutable;
}
@Override @Override
public NaturalIdLoader<?> getNaturalIdLoader() { public NaturalIdLoader<?> getNaturalIdLoader() {
return loader; return loader;

View File

@ -11,6 +11,8 @@
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener; import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener; import org.hibernate.loader.NaturalIdPreLoadListener;
@ -24,6 +26,7 @@
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
@ -38,6 +41,8 @@
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping { public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
private final SingularAttributeMapping attribute; private final SingularAttributeMapping attribute;
private final boolean immutable;
private final SimpleNaturalIdLoader<?> loader; private final SimpleNaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader; private final MultiNaturalIdLoader<?> multiLoader;
@ -49,6 +54,10 @@ public SimpleNaturalIdMapping(
super( declaringType, cacheRegionName ); super( declaringType, cacheRegionName );
this.attribute = attribute; this.attribute = attribute;
this.immutable = ! attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( null )
.isUpdatable();
this.loader = new SimpleNaturalIdLoader<>( this.loader = new SimpleNaturalIdLoader<>(
this, this,
NaturalIdPreLoadListener.NO_OP, NaturalIdPreLoadListener.NO_OP,
@ -60,12 +69,77 @@ public SimpleNaturalIdMapping(
} }
@Override @Override
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) { public void verifyFlushState(Object id, Object[] currentState, Object[] loadedState, SharedSessionContractImplementor session) {
return normalizeValue( incoming ); if ( ! immutable ) {
// EARLY EXIT!!!
// the natural id is mutable (!immutable), no need to do the checks
return;
}
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityPersister persister = getDeclaringType().getEntityPersister();
final Object naturalId = extractNaturalIdValues( currentState, session );
final Object snapshot = loadedState == null
? persistenceContext.getNaturalIdSnapshot( id, persister )
: persistenceContext.getNaturalIdHelper().extractNaturalIdValues( loadedState, persister );
if ( ! areEqual( naturalId, snapshot, session ) ) {
throw new HibernateException(
String.format(
"An immutable natural identifier of entity %s was altered from `%s` to `%s`",
persister.getEntityName(),
snapshot,
naturalId
)
);
}
}
@Override
public Object extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session) {
return state[ attribute.getStateArrayPosition() ];
}
@Override
public Object extractNaturalIdValues(Object entity, SharedSessionContractImplementor session) {
return attribute.getPropertyAccess().getGetter().get( entity );
}
@Override
public void validateInternalForm(Object naturalIdValue, SharedSessionContractImplementor session) {
if ( naturalIdValue == null ) {
return;
}
if ( naturalIdValue.getClass().isArray() ) {
// be flexible
final Object[] values = (Object[]) naturalIdValue;
if ( values.length == 1 ) {
naturalIdValue = values[0];
}
}
if ( ! getJavaTypeDescriptor().getJavaType().isInstance( naturalIdValue ) ) {
throw new IllegalArgumentException(
"Incoming natural-id value [" + naturalIdValue + "] is not of expected type ["
+ getJavaTypeDescriptor().getJavaType().getName() + "]"
);
}
}
@Override
public int calculateHashCode(Object value, SharedSessionContractImplementor session) {
return 0;
}
@Override
public Object normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session) {
return normalizeIncomingValue( incoming );
} }
@SuppressWarnings( "rawtypes" ) @SuppressWarnings( "rawtypes" )
public Object normalizeValue(Object naturalIdToLoad) { public Object normalizeIncomingValue(Object naturalIdToLoad) {
if ( naturalIdToLoad instanceof Map ) { if ( naturalIdToLoad instanceof Map ) {
final Map valueMap = (Map) naturalIdToLoad; final Map valueMap = (Map) naturalIdToLoad;
assert valueMap.size() == 1; assert valueMap.size() == 1;
@ -101,6 +175,11 @@ public List<SingularAttributeMapping> getNaturalIdAttributes() {
return Collections.singletonList( attribute ); return Collections.singletonList( attribute );
} }
@Override
public boolean isImmutable() {
return immutable;
}
@Override @Override
public MappingType getPartMappingType() { public MappingType getPartMappingType() {
return attribute.getPartMappingType(); return attribute.getPartMappingType();

View File

@ -5010,7 +5010,7 @@ private void handleNaturalIdReattachment(Object entity, SharedSessionContractImp
// for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the // for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the
// database, because we have no other way to know if the state changed while detached. // database, because we have no other way to know if the state changed while detached.
final Object[] naturalIdSnapshot; final Object naturalIdSnapshot;
final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this ); final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this );
if ( entitySnapshot == StatefulPersistenceContext.NO_ROW ) { if ( entitySnapshot == StatefulPersistenceContext.NO_ROW ) {
naturalIdSnapshot = null; naturalIdSnapshot = null;
@ -5019,12 +5019,9 @@ private void handleNaturalIdReattachment(Object entity, SharedSessionContractImp
naturalIdSnapshot = naturalIdHelper.extractNaturalIdValues( entitySnapshot, this ); naturalIdSnapshot = naturalIdHelper.extractNaturalIdValues( entitySnapshot, this );
} }
naturalIdHelper.removeSharedNaturalIdCrossReference( this, id, naturalIdSnapshot ); naturalIdHelper.removeSharedResolution( this, id, naturalIdSnapshot );
naturalIdHelper.manageLocalNaturalIdCrossReference( naturalIdHelper.manageLocalResolution(
this, id, naturalIdHelper.extractNaturalIdValues( entity, this ), this,
id,
naturalIdHelper.extractNaturalIdValues( entity, this ),
naturalIdSnapshot,
CachedNaturalIdValueSource.UPDATE CachedNaturalIdValueSource.UPDATE
); );
} }

View File

@ -659,7 +659,7 @@ public void initializeInstance(RowProcessingState rowProcessingState) {
} }
if ( entityDescriptor.getNaturalIdMapping() != null ) { if ( entityDescriptor.getNaturalIdMapping() != null ) {
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
entityDescriptor, entityDescriptor,
entityIdentifier, entityIdentifier,
persistenceContext.getNaturalIdHelper() persistenceContext.getNaturalIdHelper()

View File

@ -74,7 +74,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
assertEquals(key.hashCode(), keyClone.hashCode()); assertEquals(key.hashCode(), keyClone.hashCode());
assertEquals(key.toString(), keyClone.toString()); assertEquals(key.toString(), keyClone.toString());
assertEquals(key.getEntityName(), keyClone.getEntityName()); assertEquals(key.getEntityName(), keyClone.getEntityName());
assertArrayEquals(key.getNaturalIdValues(), keyClone.getNaturalIdValues()); assertEquals(key.getNaturalIdValues(), keyClone.getNaturalIdValues());
assertEquals(key.getTenantId(), keyClone.getTenantId()); assertEquals(key.getTenantId(), keyClone.getTenantId());
} }