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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
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 );
}
@ -62,7 +62,7 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
return ((CacheKeyImplementation) cacheKey).getId();
}
public static Object[] staticGetNaturalIdValues(Object cacheKey) {
public static Object staticGetNaturalIdValues(Object cacheKey) {
return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues();
}
@ -77,7 +77,7 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
}
@Override
public Object createNaturalIdKey(Object[] naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
public Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
return staticCreateNaturalIdKey(naturalIdValues, persister, session);
}
@ -92,7 +92,7 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
}
@Override
public Object[] getNaturalIdValues(Object cacheKey) {
public Object getNaturalIdValues(Object cacheKey) {
return staticGetNaturalIdValues(cacheKey);
}
}

View File

@ -9,13 +9,12 @@ package org.hibernate.cache.internal;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
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;
/**
@ -28,7 +27,7 @@ import org.hibernate.type.Type;
* @author Steve Ebersole
*/
public class NaturalIdCacheKey implements Serializable {
private final Object[] naturalIdValues;
private final Object naturalIdValues;
private final String entityName;
private final String tenantId;
private final int hashCode;
@ -43,40 +42,20 @@ public class NaturalIdCacheKey implements Serializable {
* @param session The originating session
*/
public NaturalIdCacheKey(
final Object[] naturalIdValues,
Type[] propertyTypes, int[] naturalIdPropertyIndexes, final String entityName,
final Object naturalIdValues,
Type[] propertyTypes,
int[] naturalIdPropertyIndexes,
final String entityName,
final SharedSessionContractImplementor session) {
this.entityName = entityName;
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();
}
@ -87,15 +66,20 @@ public class NaturalIdCacheKey implements Serializable {
public String initialize() {
//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.
final StringBuilder toStringBuilder = new StringBuilder().append( entityName ).append(
"##NaturalId[" );
for ( int i = 0; i < naturalIdValues.length; i++ ) {
toStringBuilder.append( naturalIdValues[i] );
if ( i + 1 < naturalIdValues.length ) {
toStringBuilder.append( ", " );
final StringBuilder toStringBuilder = new StringBuilder()
.append( entityName ).append( "##NaturalId[" );
if ( naturalIdValues instanceof Object[] ) {
final Object[] values = (Object[]) naturalIdValues;
for ( int i = 0; i < values.length; i++ ) {
toStringBuilder.append( values[ i ] );
if ( i + 1 < values.length ) {
toStringBuilder.append( ", " );
}
}
}
toStringBuilder.append( "]" );
else {
toStringBuilder.append( naturalIdValues );
}
return toStringBuilder.toString();
}
@ -114,7 +98,7 @@ public class NaturalIdCacheKey implements Serializable {
}
@SuppressWarnings( {"UnusedDeclaration"})
public Object[] getNaturalIdValues() {
public Object getNaturalIdValues() {
return naturalIdValues;
}
@ -145,7 +129,7 @@ public class NaturalIdCacheKey implements Serializable {
final NaturalIdCacheKey other = (NaturalIdCacheKey) o;
return Objects.equals( entityName, other.entityName )
&& Objects.equals( tenantId, other.tenantId )
&& Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues );
&& Objects.deepEquals( this.naturalIdValues, other.naturalIdValues );
}
private void readObject(ObjectInputStream ois)

View File

@ -32,7 +32,7 @@ public class SimpleCacheKeysFactory implements CacheKeysFactory {
}
@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
return new NaturalIdCacheKey(naturalIdValues, persister.getPropertyTypes(), persister.getNaturalIdentifierProperties(), null, session);
}
@ -48,7 +48,7 @@ public class SimpleCacheKeysFactory implements CacheKeysFactory {
}
@Override
public Object[] getNaturalIdValues(Object cacheKey) {
public Object getNaturalIdValues(Object cacheKey) {
return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues();
}
}

View File

@ -19,11 +19,11 @@ public interface CacheKeysFactory {
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 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
*/
Object generateCacheKey(
Object[] naturalIdValues,
Object naturalIdValues,
EntityPersister rootEntityDescriptor,
SharedSessionContractImplementor session);
@ -58,7 +58,7 @@ public interface NaturalIdDataAccess extends CachedDomainDataAccess {
*
* @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),

View File

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

View File

@ -53,14 +53,14 @@ public class NaturalIdReadWriteAccess extends AbstractReadWriteAccess implements
@Override
public Object generateCacheKey(
Object[] naturalIdValues,
Object naturalIdValues,
EntityPersister rootEntityDescriptor,
SharedSessionContractImplementor session) {
return keysFactory.createNaturalIdKey( naturalIdValues, rootEntityDescriptor, session );
}
@Override
public Object[] getNaturalIdValues(Object cacheKey) {
public Object getNaturalIdValues(Object cacheKey) {
return keysFactory.getNaturalIdValues( cacheKey );
}

View File

@ -408,11 +408,8 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
}
setStatus( Status.MANAGED );
loadedState = getPersister().getPropertyValues( entity );
getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference(
persister,
id,
loadedState,
null,
getPersistenceContext().getNaturalIdHelper().manageLocalResolution(
id, loadedState, persister,
CachedNaturalIdValueSource.LOAD
);
}

View File

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

View File

@ -61,6 +61,7 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap;
import org.hibernate.internal.util.collections.IdentityMap;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
@ -328,7 +329,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
@Override
public Object[] getNaturalIdSnapshot(Object id, EntityPersister persister) throws HibernateException {
public Object getNaturalIdSnapshot(Object id, EntityPersister persister) throws HibernateException {
if ( !persister.hasNaturalIdentifier() ) {
return null;
}
@ -336,7 +337,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
persister = locateProperPersister( persister );
// let's first see if it is part of the natural id cache...
final Object[] cachedValue = naturalIdHelper.findCachedNaturalId( persister, id );
final Object cachedValue = naturalIdHelper.findCachedNaturalId( persister, id );
if ( cachedValue != null ) {
return cachedValue;
}
@ -345,7 +346,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
naturalIdHelper.cacheResolutionFromLoad(
persister,
id,
dbValue
@ -365,7 +366,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
for ( int i = 0; i < props.length; i++ ) {
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
}
naturalIdHelper.cacheNaturalIdCrossReferenceFromLoad(
naturalIdHelper.cacheResolutionFromLoad(
persister,
id,
naturalIdSnapshotSubSet
@ -1947,10 +1948,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() {
@Override
public void cacheNaturalIdCrossReferenceFromLoad(
public void cacheResolutionFromLoad(
EntityPersister persister,
Object id,
Object[] naturalIdValues) {
Object naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
@ -1962,19 +1963,18 @@ public class StatefulPersistenceContext implements PersistenceContext {
// from a single load event. The first put journal would come from the natural id resolution;
// the second comes from the entity loading. In this condition, we want to avoid the multiple
// 'put' stats incrementing.
final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues );
final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalIdValues );
if ( justAddedLocally && persister.hasNaturalIdCache() ) {
managedSharedCacheEntries( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD );
managedSharedResolutions( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD );
}
}
@Override
public void manageLocalNaturalIdCrossReference(
EntityPersister persister,
public void manageLocalResolution(
Object id,
Object[] state,
Object[] previousState,
Object naturalId,
EntityPersister persister,
CachedNaturalIdValueSource source) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
@ -1982,18 +1982,17 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
// cache
getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues );
getNaturalIdXrefDelegate().cacheResolution( persister, id, naturalId );
}
@Override
public void manageSharedNaturalIdCrossReference(
EntityPersister persister,
public void manageSharedResolution(
final Object id,
Object[] state,
Object[] previousState,
Object naturalId,
Object previousNaturalId,
EntityPersister persister,
CachedNaturalIdValueSource source) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
@ -2006,17 +2005,17 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
persister = locateProperPersister( persister );
final Object[] naturalIdValues = extractNaturalIdValues( state, persister );
final Object[] previousNaturalIdValues = previousState == null ? null : extractNaturalIdValues( previousState, persister );
final Object naturalIdValues = extractNaturalIdValues( naturalId, 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,
final Object id,
Object[] naturalIdValues,
Object[] previousNaturalIdValues,
Object naturalIdValues,
Object previousNaturalIdValues,
CachedNaturalIdValueSource source) {
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session );
@ -2133,26 +2132,25 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
@Override
public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Object id, Object[] state) {
public Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return null;
}
persister = locateProperPersister( persister );
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference(
final Object localNaturalIdValues = getNaturalIdXrefDelegate().removeResolutions(
persister,
id,
naturalIdValues
id,
naturalId
);
return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues;
return localNaturalIdValues != null ? localNaturalIdValues : naturalId;
}
@Override
public void removeSharedNaturalIdCrossReference(EntityPersister persister, Object id, Object[] naturalIdValues) {
public void removeSharedResolution(EntityPersister persister, Object id, Object naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
@ -2180,46 +2178,37 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
@Override
public Object[] findCachedNaturalId(EntityPersister persister, Object pk) {
public Object findCachedNaturalId(EntityPersister persister, Object pk) {
return getNaturalIdXrefDelegate().findCachedNaturalId( locateProperPersister( persister ), pk );
}
@Override
public Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
return getNaturalIdXrefDelegate().findCachedNaturalIdResolution( locateProperPersister( persister ), naturalIdValues );
return getNaturalIdXrefDelegate().findResolution( locateProperPersister( persister ), naturalIdValues );
}
@Override
public Object[] extractNaturalIdValues(Object[] state, EntityPersister persister) {
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
if ( state.length == naturalIdPropertyIndexes.length ) {
return state;
}
public Object extractNaturalIdValues(Object[] state, EntityPersister persister) {
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
assert naturalIdMapping != null;
final Object[] naturalIdValues = new Object[naturalIdPropertyIndexes.length];
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
naturalIdValues[i] = state[naturalIdPropertyIndexes[i]];
}
return naturalIdValues;
return naturalIdMapping.extractNaturalIdValues( state, getSession() );
}
@Override
public Object[] extractNaturalIdValues(Object entity, EntityPersister persister) {
public Object extractNaturalIdValues(Object entity, EntityPersister persister) {
if ( entity == null ) {
throw new AssertionFailure( "Entity from which to extract natural id value(s) cannot be null" );
}
if ( persister == null ) {
throw new AssertionFailure( "Persister to use in extracting natural id value(s) cannot be null" );
}
final int[] naturalIdentifierProperties = persister.getNaturalIdentifierProperties();
final Object[] naturalIdValues = new Object[naturalIdentifierProperties.length];
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
assert naturalIdMapping != null;
for ( int i = 0; i < naturalIdentifierProperties.length; i++ ) {
naturalIdValues[i] = persister.getPropertyValue( entity, naturalIdentifierProperties[i] );
}
return naturalIdValues;
return naturalIdMapping.extractNaturalIdValues( entity, session );
}
@Override
@ -2236,7 +2225,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
persister = locateProperPersister( persister );
final Object[] naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister );
final Object naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister );
final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate();
final boolean changed = ! naturalIdXrefDelegate.sameAsCached(
persister,
@ -2245,11 +2234,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
);
if ( changed ) {
final Object[] cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, pk, naturalIdValuesFromCurrentObjectState );
final Object cachedNaturalIdValues = naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
naturalIdXrefDelegate.cacheResolution( persister, pk, naturalIdValuesFromCurrentObjectState );
naturalIdXrefDelegate.stashInvalidNaturalIdReference( persister, cachedNaturalIdValues );
removeSharedNaturalIdCrossReference(
removeSharedResolution(
persister,
pk,
cachedNaturalIdValues
@ -2264,7 +2253,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
@Override
public void handleEviction(Object object, EntityPersister persister, Serializable identifier) {
getNaturalIdXrefDelegate().removeNaturalIdCrossReference(
getNaturalIdXrefDelegate().removeResolutions(
persister,
identifier,
findCachedNaturalId( persister, identifier )
@ -2276,15 +2265,4 @@ public class StatefulPersistenceContext implements PersistenceContext {
public NaturalIdHelper getNaturalIdHelper() {
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 final class TwoPhaseLoad {
}
if ( persister.hasNaturalIdentifier() ) {
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
persister,
id,
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.
*/
Object[] getNaturalIdSnapshot(Object id, EntityPersister persister);
Object getNaturalIdSnapshot(Object id, EntityPersister persister);
/**
* Add a canonical mapping from entity key to entity instance
@ -814,7 +814,7 @@ public interface PersistenceContext {
*
* @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
@ -824,35 +824,27 @@ public interface PersistenceContext {
*
* @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.
* 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 naturalIdValues The natural id values
*/
void cacheNaturalIdCrossReferenceFromLoad(
void cacheResolutionFromLoad(
EntityPersister persister,
Object id,
Object[] naturalIdValues);
Object naturalIdValues);
/**
* Creates necessary local cross-reference entries.
*
* @param persister The persister representing the entity type.
* @param id The primary key value
* @param state Generally the "full entity state array", though could also be the natural id values array
* @param previousState Generally the "full entity state array", though could also be the natural id values array.
* Specifically represents the previous values on update, and so is only used with {@link CachedNaturalIdValueSource#UPDATE}
* @param source Enumeration representing how these values are coming into cache.
*/
void manageLocalNaturalIdCrossReference(
EntityPersister persister,
void manageLocalResolution(
Object id,
Object[] state,
Object[] previousState,
Object naturalId,
EntityPersister persister,
CachedNaturalIdValueSource source);
/**
@ -860,37 +852,29 @@ public interface PersistenceContext {
*
* @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 naturalId Generally the "full entity state array", though could also be the natural id values array
*
* @return The local cached natural id values (could be different from given values).
*/
Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Object id, Object[] state);
Object removeLocalResolution(EntityPersister persister, Object id, Object naturalId);
/**
* 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(
EntityPersister persister,
void manageSharedResolution(
Object id,
Object[] state,
Object[] previousState,
Object naturalId,
Object previousNaturalId,
EntityPersister persister,
CachedNaturalIdValueSource source);
/**
* 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 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.
@ -900,7 +884,7 @@ public interface PersistenceContext {
*
* @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

View File

@ -22,7 +22,6 @@ import org.hibernate.engine.internal.Nullability;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
@ -38,9 +37,9 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type;
@ -103,48 +102,17 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
}
}
if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) {
if ( !persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// EARLY EXIT!!!
// 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()
)
)
);
}
}
final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping();
if ( naturalIdMapping == null ) {
return;
}
if ( entry.getStatus() == Status.READ_ONLY ) {
// nothing to check
return;
}
naturalIdMapping.verifyFlushState( entry.getId(), current, loaded, session );
}
/**

View File

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

View File

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

View File

@ -75,7 +75,7 @@ public class MultiNaturalIdLoaderStandard<E> implements MultiNaturalIdLoader<E>
(naturalId, session1) -> {
// `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
return entityDescriptor.getNaturalIdMapping().normalizeValue( naturalId, session );
return entityDescriptor.getNaturalIdMapping().normalizeIncomingValue( naturalId, session );
},
session.getLoadQueryInfluencers(),
lockOptions,

View File

@ -47,7 +47,7 @@ public class MultiNaturalIdLoadingBatcher {
* the "true" form - single value for simple natural-ids and an array for
* 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);
}

View File

@ -61,7 +61,7 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
SharedSessionContractImplementor session) {
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 );
jdbcParamBindings.registerParametersForEachJdbcValue(
@ -75,7 +75,7 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
@Override
protected Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session) {
return naturalIdMapping().normalizeValue( naturalIdToLoad, session );
return naturalIdMapping().normalizeIncomingValue( naturalIdToLoad, session );
}
@Override
@ -161,7 +161,7 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
public Object resolveNaturalIdToId(
Object naturalIdValue,
SharedSessionContractImplementor session) {
final Object bindValue = naturalIdMapping().normalizeValue( naturalIdValue, session );
final Object bindValue = naturalIdMapping().normalizeIncomingValue( naturalIdValue, session );
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();

View File

@ -6,9 +6,11 @@
*/
package org.hibernate.metamodel.mapping;
import java.util.Objects;
import java.util.function.BiConsumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
@ -103,4 +105,9 @@ public interface ModelPart extends MappingModelExpressable {
}
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 @@ package org.hibernate.metamodel.mapping;
import java.util.List;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
@ -23,6 +24,21 @@ public interface NaturalIdMapping extends VirtualModelPart {
*/
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
default String getPartName() {
return PART_NAME;
@ -31,5 +47,40 @@ public interface NaturalIdMapping extends VirtualModelPart {
NaturalIdLoader getNaturalIdLoader();
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 abstract class AbstractNaturalIdMapping implements NaturalIdMapping {
return role;
}
public EntityMappingType getDeclaringType() {
return declaringType;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return declaringType;

View File

@ -7,11 +7,14 @@
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
@ -25,6 +28,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
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<JdbcMapping> jdbcMappings;
private final boolean immutable;
private final NaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader;
@ -54,11 +59,15 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
super( declaringType, cacheRegionName );
this.attributes = attributes;
boolean anyMutable = false;
final List<JdbcMapping> jdbcMappings = new ArrayList<>();
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.immutable = ! anyMutable;
loader = new CompoundNaturalIdLoader<>(
this,
@ -70,11 +79,43 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
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
@SuppressWarnings( "rawtypes" )
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) {
public Object[] normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session) {
if ( incoming instanceof Object[] ) {
return incoming;
return (Object[]) incoming;
}
if ( incoming instanceof Map ) {
@ -90,11 +131,95 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
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
public List<SingularAttributeMapping> getNaturalIdAttributes() {
return attributes;
}
@Override
public boolean isImmutable() {
return immutable;
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
return loader;

View File

@ -11,6 +11,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
@ -24,6 +26,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlSelection;
@ -38,6 +41,8 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
private final SingularAttributeMapping attribute;
private final boolean immutable;
private final SimpleNaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader;
@ -49,6 +54,10 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
super( declaringType, cacheRegionName );
this.attribute = attribute;
this.immutable = ! attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( null )
.isUpdatable();
this.loader = new SimpleNaturalIdLoader<>(
this,
NaturalIdPreLoadListener.NO_OP,
@ -60,12 +69,77 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
}
@Override
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) {
return normalizeValue( incoming );
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 );
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" )
public Object normalizeValue(Object naturalIdToLoad) {
public Object normalizeIncomingValue(Object naturalIdToLoad) {
if ( naturalIdToLoad instanceof Map ) {
final Map valueMap = (Map) naturalIdToLoad;
assert valueMap.size() == 1;
@ -101,6 +175,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
return Collections.singletonList( attribute );
}
@Override
public boolean isImmutable() {
return immutable;
}
@Override
public MappingType getPartMappingType() {
return attribute.getPartMappingType();

View File

@ -5010,7 +5010,7 @@ public abstract class AbstractEntityPersister
// 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.
final Object[] naturalIdSnapshot;
final Object naturalIdSnapshot;
final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this );
if ( entitySnapshot == StatefulPersistenceContext.NO_ROW ) {
naturalIdSnapshot = null;
@ -5019,12 +5019,9 @@ public abstract class AbstractEntityPersister
naturalIdSnapshot = naturalIdHelper.extractNaturalIdValues( entitySnapshot, this );
}
naturalIdHelper.removeSharedNaturalIdCrossReference( this, id, naturalIdSnapshot );
naturalIdHelper.manageLocalNaturalIdCrossReference(
this,
id,
naturalIdHelper.extractNaturalIdValues( entity, this ),
naturalIdSnapshot,
naturalIdHelper.removeSharedResolution( this, id, naturalIdSnapshot );
naturalIdHelper.manageLocalResolution(
id, naturalIdHelper.extractNaturalIdValues( entity, this ), this,
CachedNaturalIdValueSource.UPDATE
);
}

View File

@ -659,7 +659,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
}
if ( entityDescriptor.getNaturalIdMapping() != null ) {
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
persistenceContext.getNaturalIdHelper().cacheResolutionFromLoad(
entityDescriptor,
entityIdentifier,
persistenceContext.getNaturalIdHelper()

View File

@ -74,7 +74,7 @@ public class NaturalIdCacheKeyTest {
assertEquals(key.hashCode(), keyClone.hashCode());
assertEquals(key.toString(), keyClone.toString());
assertEquals(key.getEntityName(), keyClone.getEntityName());
assertArrayEquals(key.getNaturalIdValues(), keyClone.getNaturalIdValues());
assertEquals(key.getNaturalIdValues(), keyClone.getNaturalIdValues());
assertEquals(key.getTenantId(), keyClone.getTenantId());
}