natural-id caching

- on top of Christian's PR #3735 which fixes a problem with pulling entity snapshots from the database which effects natural-id handling and caused test failures here (b4 #3735)
This commit is contained in:
Steve Ebersole 2021-02-24 14:25:19 -06:00
parent 37b03ecc05
commit e5c239c7c8
21 changed files with 188 additions and 95 deletions

View File

@ -162,12 +162,12 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
* Handle sending notifications needed for natural-id before saving
*/
protected void handleNaturalIdPreSaveNotifications() {
// before save, we need to add a local (transactional) natural id cross-reference
// before save, we need to add a natural id cross-reference to the persistence-context
final NaturalIdMapping naturalIdMapping = getPersister().getNaturalIdMapping();
if ( naturalIdMapping != null ) {
getSession().getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
getId(),
naturalIdMapping.extractNaturalIdValues( state, getSession() ),
naturalIdMapping.extractNaturalIdFromEntityState( state, getSession() ),
getPersister(),
CachedNaturalIdValueSource.INSERT
);
@ -185,7 +185,7 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
return;
}
final Object naturalIdValues = naturalIdMapping.extractNaturalIdValues( state, getSession() );
final Object naturalIdValues = naturalIdMapping.extractNaturalIdFromEntityState( state, getSession() );
if ( isEarlyInsert() ) {
// with early insert, we still need to add a local (transactional) natural id cross-reference

View File

@ -65,7 +65,7 @@ public class EntityDeleteAction extends EntityAction {
if ( naturalIdMapping != null ) {
naturalIdValues = session.getPersistenceContextInternal().getNaturalIdResolutions().removeLocalResolution(
getId(),
naturalIdMapping.extractNaturalIdValues( state, session ),
naturalIdMapping.extractNaturalIdFromEntityState( state, session ),
getPersister()
);
}

View File

@ -21,7 +21,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostCommitUpdateEventListener;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
@ -94,7 +93,7 @@ public class EntityUpdateAction extends EntityAction {
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, naturalIdMapping, id, previousState, session );
session.getPersistenceContextInternal().getNaturalIdResolutions().manageLocalResolution(
id,
naturalIdMapping.extractNaturalIdValues( state, session ),
naturalIdMapping.extractNaturalIdFromEntityState( state, session ),
persister,
CachedNaturalIdValueSource.UPDATE
);
@ -109,7 +108,7 @@ public class EntityUpdateAction extends EntityAction {
SharedSessionContractImplementor session) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( previousState != null ) {
return naturalIdMapping.extractNaturalIdValues( previousState, session );
return naturalIdMapping.extractNaturalIdFromEntityState( previousState, session );
}
return persistenceContext.getNaturalIdSnapshot( id, persister );
@ -226,7 +225,7 @@ public class EntityUpdateAction extends EntityAction {
if ( naturalIdMapping != null ) {
session.getPersistenceContextInternal().getNaturalIdResolutions().manageSharedResolution(
id,
naturalIdMapping.extractNaturalIdValues( state, session ),
naturalIdMapping.extractNaturalIdFromEntityState( state, session ),
previousNaturalIdValues,
persister,
CachedNaturalIdValueSource.UPDATE

View File

@ -8,11 +8,13 @@ package org.hibernate.boot;
import org.jboss.logging.Logger;
import static org.hibernate.internal.CoreLogging.subsystemLoggerName;
/**
* @author Steve Ebersole
* Logging related to Hibernate bootstrapping
*/
public class BootLogging {
public static final String NAME = "org.hibernate.orm.boot";
public static String NAME = subsystemLoggerName( "boot" );
public static final Logger LOGGER = Logger.getLogger( NAME );

View File

@ -25,6 +25,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdLogging;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.results.LoadingLogger;
@ -66,6 +67,13 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
@Override
public void cacheResolutionFromLoad(Object id, Object naturalId, EntityMappingType entityDescriptor) {
NaturalIdLogging.LOGGER.debugf(
"Caching resolution natural-id resolution from load (%s) : `%s` -> `%s`",
entityDescriptor.getEntityName(),
naturalId,
id
);
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
@ -88,11 +96,18 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
/**
* Private, but see {@link #cacheResolution} for public version
*/
private boolean cacheResolutionLocally(Object pk, Object naturalId, EntityMappingType entityDescriptor) {
private boolean cacheResolutionLocally(Object id, Object naturalId, EntityMappingType entityDescriptor) {
// by the time we get here we assume that the natural-id value has already been validated so just do an assert
assert entityDescriptor.getNaturalIdMapping() != null;
assert isValidValue( naturalId, entityDescriptor );
NaturalIdLogging.LOGGER.debugf(
"Locally caching natural-id resolution (%s) : `%s` -> `%s`",
entityDescriptor.getEntityName(),
naturalId,
id
);
final EntityMappingType rootEntityDescriptor = entityDescriptor.getRootEntityDescriptor();
final EntityResolutions previousEntry = resolutionsByEntity.get( rootEntityDescriptor );
final EntityResolutions resolutions;
@ -104,7 +119,7 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
resolutionsByEntity.put( rootEntityDescriptor, resolutions );
}
return resolutions.cache( pk, naturalId );
return resolutions.cache( id, naturalId );
}
@Override
@ -157,6 +172,13 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
@Override
public Object removeLocalResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
NaturalIdLogging.LOGGER.debugf(
"Removing locally cached natural-id resolution (%s) : `%s` -> `%s`",
entityDescriptor.getEntityName(),
naturalId,
id
);
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( naturalIdMapping == null ) {
@ -175,6 +197,11 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
return localNaturalIdValues != null ? localNaturalIdValues : naturalId;
}
/**
* Removes the cross-reference from both Session cache (Resolution) as well as the
* second-level cache (NaturalIdDataAccess). Returns the natural-id value previously
* cached on the Session
*/
private Object removeNaturalIdCrossReference(Object id, Object naturalIdValue, EntityPersister persister) {
validateNaturalId( persister, naturalIdValue );
@ -238,24 +265,29 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
Object naturalIdValues,
Object previousNaturalIdValues,
CachedNaturalIdValueSource source) {
final NaturalIdDataAccess cacheAccess = persister.getNaturalIdMapping().getCacheAccess();
if ( cacheAccess == null ) {
return;
}
final EntityMappingType rootEntityDescriptor = persister.getRootEntityDescriptor();
final EntityPersister rootEntityPersister = rootEntityDescriptor.getEntityPersister();
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, rootEntityPersister, session() );
final Object cacheKey = cacheAccess.generateCacheKey( naturalIdValues, rootEntityPersister, session() );
final SessionFactoryImplementor factory = session().getFactory();
final StatisticsImplementor statistics = factory.getStatistics();
switch ( source ) {
case LOAD: {
if ( CacheHelper.fromSharedCache( session(), naturalIdCacheKey, naturalIdCacheAccessStrategy ) != null ) {
if ( CacheHelper.fromSharedCache( session(), cacheKey, cacheAccess ) != null ) {
// prevent identical re-cachings
return;
}
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
final boolean put = cacheAccess.putFromLoad(
session(),
naturalIdCacheKey,
cacheKey,
id,
null
);
@ -263,34 +295,34 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
if ( put && statistics.isStatisticsEnabled() ) {
statistics.naturalIdCachePut(
rootEntityDescriptor.getNavigableRole(),
naturalIdCacheAccessStrategy.getRegion().getName()
cacheAccess.getRegion().getName()
);
}
break;
}
case INSERT: {
final boolean put = naturalIdCacheAccessStrategy.insert( session(), naturalIdCacheKey, id );
final boolean put = cacheAccess.insert( session(), cacheKey, id );
if ( put && statistics.isStatisticsEnabled() ) {
statistics.naturalIdCachePut(
rootEntityDescriptor.getNavigableRole(),
naturalIdCacheAccessStrategy.getRegion().getName()
cacheAccess.getRegion().getName()
);
}
( (EventSource) session() ).getActionQueue().registerProcess(
(success, session) -> {
if ( success ) {
final boolean put1 = naturalIdCacheAccessStrategy.afterInsert( session, naturalIdCacheKey, id );
final boolean put1 = cacheAccess.afterInsert( session, cacheKey, id );
if ( put1 && statistics.isStatisticsEnabled() ) {
statistics.naturalIdCachePut(
rootEntityDescriptor.getNavigableRole(),
naturalIdCacheAccessStrategy.getRegion().getName()
cacheAccess.getRegion().getName()
);
}
}
else {
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
cacheAccess.evict( cacheKey );
}
}
);
@ -298,30 +330,30 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
break;
}
case UPDATE: {
final Object previousCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( previousNaturalIdValues, rootEntityPersister, session() );
if ( naturalIdCacheKey.equals( previousCacheKey ) ) {
final Object previousCacheKey = cacheAccess.generateCacheKey( previousNaturalIdValues, rootEntityPersister, session() );
if ( cacheKey.equals( previousCacheKey ) ) {
// prevent identical re-caching, solves HHH-7309
return;
}
final SoftLock removalLock = naturalIdCacheAccessStrategy.lockItem( session(), previousCacheKey, null );
naturalIdCacheAccessStrategy.remove( session(), previousCacheKey);
final SoftLock removalLock = cacheAccess.lockItem( session(), previousCacheKey, null );
cacheAccess.remove( session(), previousCacheKey);
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( session(), naturalIdCacheKey, null );
final boolean put = naturalIdCacheAccessStrategy.update( session(), naturalIdCacheKey, id );
final SoftLock lock = cacheAccess.lockItem( session(), cacheKey, null );
final boolean put = cacheAccess.update( session(), cacheKey, id );
if ( put && statistics.isStatisticsEnabled() ) {
statistics.naturalIdCachePut(
rootEntityDescriptor.getNavigableRole(),
naturalIdCacheAccessStrategy.getRegion().getName()
cacheAccess.getRegion().getName()
);
}
( (EventSource) session() ).getActionQueue().registerProcess(
(success, session) -> {
naturalIdCacheAccessStrategy.unlockItem( session(), previousCacheKey, removalLock );
cacheAccess.unlockItem( session(), previousCacheKey, removalLock );
if (success) {
final boolean put12 = naturalIdCacheAccessStrategy.afterUpdate(
final boolean put12 = cacheAccess.afterUpdate(
session(),
naturalIdCacheKey,
cacheKey,
id,
lock
);
@ -329,12 +361,12 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
if ( put12 && statistics.isStatisticsEnabled() ) {
statistics.naturalIdCachePut(
rootEntityDescriptor.getNavigableRole(),
naturalIdCacheAccessStrategy.getRegion().getName()
cacheAccess.getRegion().getName()
);
}
}
else {
naturalIdCacheAccessStrategy.unlockItem( session(), naturalIdCacheKey, lock );
cacheAccess.unlockItem( session(), cacheKey, lock );
}
}
);
@ -352,7 +384,14 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
@Override
public void removeSharedResolution(Object id, Object naturalId, EntityMappingType entityDescriptor) {
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( naturalIdMapping == null || naturalIdMapping.getCacheAccess() == null ) {
if ( naturalIdMapping == null ) {
// nothing to do
return;
}
final NaturalIdDataAccess cacheAccess = naturalIdMapping.getCacheAccess();
if ( cacheAccess == null ) {
// nothing to do
return;
}
@ -363,14 +402,13 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalId, persister, session() );
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
final Object naturalIdCacheKey = cacheAccess.generateCacheKey( naturalId, persister, session() );
cacheAccess.evict( naturalIdCacheKey );
// if ( sessionCachedNaturalIdValues != null
// && !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
// final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session );
// naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
// cacheAccess.evict( sessionNaturalIdCacheKey );
// }
}
@ -383,7 +421,7 @@ public class NaturalIdResolutionsImpl implements NaturalIdResolutions, Serializa
}
final EntityPersister persister = locatePersisterForKey( entityDescriptor.getEntityPersister() );
final Object naturalIdValuesFromCurrentObjectState = naturalIdMapping.extractNaturalIdValues( entity, session() );
final Object naturalIdValuesFromCurrentObjectState = naturalIdMapping.extractNaturalIdFromEntity( entity, session() );
final boolean changed = ! sameAsCached(
persister,
pk,

View File

@ -24,8 +24,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.event.spi.PreLoadEvent;
@ -279,8 +277,8 @@ public final class TwoPhaseLoad {
final SessionFactoryImplementor factory = session.getFactory();
final StatisticsImplementor statistics = factory.getStatistics();
if ( persister.canWriteToCache() && session.getCacheMode().isPutEnabled() ) {
if ( persister.canWriteToCache() && session.getCacheMode().isPutEnabled() ) {
if ( debugEnabled ) {
LOG.debugf(
"Adding entity to second-level cache: %s",
@ -335,7 +333,9 @@ public final class TwoPhaseLoad {
if ( persister.hasNaturalIdentifier() ) {
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
id, persister.getNaturalIdMapping().extractNaturalIdValues( hydratedState, session ), persister
id,
persister.getNaturalIdMapping().extractNaturalIdFromEntityState( hydratedState, session ),
persister
);
}

View File

@ -51,6 +51,7 @@ public interface NaturalIdResolutions {
/**
* Removes any local cross-reference, returning the previously cached value if one.
*
* Again, this only effects the persistence-context cache, not the L2 cache
*/
Object removeLocalResolution(Object id, Object naturalId, EntityMappingType entityDescriptor);

View File

@ -533,7 +533,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
if ( entity != null && persister.hasNaturalIdentifier() ) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
event.getEntityId(), persister.getNaturalIdMapping().extractNaturalIdValues( entity, session ), persister
event.getEntityId(), persister.getNaturalIdMapping().extractNaturalIdFromEntity( entity, session ), persister
);
}

View File

@ -14,6 +14,10 @@ import org.jboss.logging.Logger;
* @author Steve Ebersole
*/
public class CoreLogging {
public static String subsystemLoggerName(String subsystem) {
return "org.hibernate.orm." + subsystem;
}
/**
* Disallow instantiation
*/

View File

@ -28,6 +28,8 @@ import org.hibernate.query.sqm.sql.FromClauseIndex;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
@ -105,10 +107,13 @@ class DatabaseSnapshotExecutor {
// We produce the same state array as if we were creating an entity snapshot
final List<DomainResult> domainResults = new ArrayList<>();
final SqlExpressionResolver sqlExpressionResolver = state.getSqlExpressionResolver();
// We just need a literal to have a result set
domainResults.add(
new QueryLiteral<>( null, IntegerType.INSTANCE ).createDomainResult( null, state )
);
entityDescriptor.getIdentifierMapping().forEachSelection(
(columnIndex, selection) -> {
final TableReference tableReference = rootTableGroup.resolveTableReference( selection.getContainingTableExpression() );
@ -116,7 +121,7 @@ class DatabaseSnapshotExecutor {
final JdbcParameter jdbcParameter = new JdbcParameterImpl( selection.getJdbcMapping() );
jdbcParameters.add( jdbcParameter );
final ColumnReference columnReference = (ColumnReference) state.getSqlExpressionResolver()
final ColumnReference columnReference = (ColumnReference) sqlExpressionResolver
.resolveSqlExpression(
createColumnReferenceKey( tableReference, selection.getSelectionExpression() ),
s -> new ColumnReference(

View File

@ -0,0 +1,22 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.internal.CoreLogging;
import org.jboss.logging.Logger;
/**
* Logging related to natural-id operations
*/
public interface NaturalIdLogging {
String LOGGER_NAME = CoreLogging.subsystemLoggerName( "mapping.natural_id" );
Logger LOGGER = Logger.getLogger( LOGGER_NAME );
boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
boolean TRACE_ENABLED = LOGGER.isTraceEnabled();
}

View File

@ -57,7 +57,7 @@ public interface NaturalIdMapping extends VirtualModelPart {
*
* @return The extracted natural id values. This is a normalized
*/
Object extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session);
Object extractNaturalIdFromEntityState(Object[] state, SharedSessionContractImplementor session);
/**
* Given an entity instance, extract the normalized natural id representation
@ -66,7 +66,7 @@ public interface NaturalIdMapping extends VirtualModelPart {
*
* @return The extracted natural id values
*/
Object extractNaturalIdValues(Object entity, SharedSessionContractImplementor session);
Object extractNaturalIdFromEntity(Object entity, SharedSessionContractImplementor session);
/**

View File

@ -108,7 +108,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
}
@Override
public Object[] extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session) {
public Object[] extractNaturalIdFromEntityState(Object[] state, SharedSessionContractImplementor session) {
if ( state == null ) {
return null;
}
@ -128,7 +128,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
}
@Override
public Object[] extractNaturalIdValues(Object entity, SharedSessionContractImplementor session) {
public Object[] extractNaturalIdFromEntity(Object entity, SharedSessionContractImplementor session) {
final Object[] values = new Object[ attributes.size() ];
for ( int i = 0; i < attributes.size(); i++ ) {
@ -196,11 +196,11 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityPersister persister = getDeclaringType().getEntityPersister();
final Object[] naturalId = extractNaturalIdValues( currentState, session );
final Object[] naturalId = extractNaturalIdFromEntityState( currentState, session );
final Object snapshot = loadedState == null
? persistenceContext.getNaturalIdSnapshot( id, persister )
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
: persister.getNaturalIdMapping().extractNaturalIdFromEntityState( loadedState, session );
final Object[] previousNaturalId = (Object[]) snapshot;
assert naturalId.length == getNaturalIdAttributes().size();

View File

@ -61,10 +61,10 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityPersister persister = getDeclaringType().getEntityPersister();
final Object naturalId = extractNaturalIdValues( currentState, session );
final Object naturalId = extractNaturalIdFromEntityState( currentState, session );
final Object snapshot = loadedState == null
? persistenceContext.getNaturalIdSnapshot( id, persister )
: persister.getNaturalIdMapping().extractNaturalIdValues( loadedState, session );
: persister.getNaturalIdMapping().extractNaturalIdFromEntityState( loadedState, session );
if ( ! areEqual( naturalId, snapshot, session ) ) {
throw new HibernateException(
@ -79,12 +79,20 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
}
@Override
public Object extractNaturalIdValues(Object[] state, SharedSessionContractImplementor session) {
public Object extractNaturalIdFromEntityState(Object[] state, SharedSessionContractImplementor session) {
if ( state == null ) {
return null;
}
if ( state.length == 1 ) {
return state[0];
}
return state[ attribute.getStateArrayPosition() ];
}
@Override
public Object extractNaturalIdValues(Object entity, SharedSessionContractImplementor session) {
public Object extractNaturalIdFromEntity(Object entity, SharedSessionContractImplementor session) {
return attribute.getPropertyAccess().getGetter().get( entity );
}

View File

@ -5005,11 +5005,11 @@ public abstract class AbstractEntityPersister
}
private void handleNaturalIdReattachment(Object entity, SharedSessionContractImplementor session) {
if ( !hasNaturalIdentifier() ) {
if ( naturalIdMapping == null ) {
return;
}
if ( getEntityMetamodel().hasImmutableNaturalId() ) {
if ( ! naturalIdMapping.isMutable() ) {
// we assume there were no changes to natural id during detachment for now, that is validated later
// during flush.
return;
@ -5027,13 +5027,13 @@ public abstract class AbstractEntityPersister
naturalIdSnapshot = null;
}
else {
naturalIdSnapshot = naturalIdMapping.extractNaturalIdValues( entitySnapshot, session );
naturalIdSnapshot = naturalIdMapping.extractNaturalIdFromEntityState( entitySnapshot, session );
}
naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this );
naturalIdResolutions.manageLocalResolution(
id,
naturalIdMapping.extractNaturalIdValues( entity, session ),
naturalIdMapping.extractNaturalIdFromEntity( entity, session ),
this,
CachedNaturalIdValueSource.UPDATE
);

View File

@ -25,9 +25,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PreLoadEvent;
import org.hibernate.event.spi.PreLoadEventListener;
import org.hibernate.internal.util.StringHelper;
@ -727,7 +725,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
if ( entityDescriptor.getNaturalIdMapping() != null ) {
persistenceContext.getNaturalIdResolutions().cacheResolutionFromLoad(
entityIdentifier,
entityDescriptor.getNaturalIdMapping().extractNaturalIdValues( resolvedEntityState, session ),
entityDescriptor.getNaturalIdMapping().extractNaturalIdFromEntityState( resolvedEntityState, session ),
entityDescriptor
);
}

View File

@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -74,6 +75,7 @@ public class BasicNaturalIdCachingTests {
final Object cacheKey = cacheAccess.generateCacheKey( "abc", entityPersister, session );
final Object cached = cacheAccess.get( session, cacheKey );
assertThat( cached, notNullValue() );
assertThat( cached, equalTo( 1 ) );
}
);

View File

@ -20,11 +20,11 @@ import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@DomainModel( annotatedClasses = { MappedSuperclassOverrideTest.MyMappedSuperclass.class, MappedSuperclassOverrideTest.MyEntity.class } )
@SessionFactory( exportSchema = false )
@FailureExpected( jiraKey = "HHH-12085" )
public class MappedSuperclassOverrideTest {
@Test
public void testModel(SessionFactoryScope scope) {
@ -32,7 +32,11 @@ public class MappedSuperclassOverrideTest {
.getRuntimeMetamodels()
.getEntityMappingType( MyEntity.class )
.getEntityPersister();
assertTrue( entityPersister.hasNaturalIdentifier() );
// defining a natural-id on a sub-entity is not allowed, only on the root.
// - here, because the root does not declare `#getName` as a natural-id
// the hierarchy does not define a natural-id
assertFalse( entityPersister.hasNaturalIdentifier() );
}
@MappedSuperclass
@ -77,7 +81,6 @@ public class MappedSuperclassOverrideTest {
super( id, name );
}
// this should not be allowed, and supposedly fails anyway...
@Override
@NaturalId
public String getName() {

View File

@ -47,10 +47,12 @@ public class InheritedNaturalIdCacheTest {
}
@Test
@NotImplementedYet( reason = "natural-id caching not yet implemented", strict = false )
@NotImplementedYet(
reason = "We do not throw `WrongClassException` atm, we just return null",
strict = false
)
public void testLoadingInheritedEntitiesByNaturalId(SessionFactoryScope scope) {
// load the entities "properly" by natural-id
scope.inTransaction(
(session) -> {
final MyEntity entity = session.bySimpleNaturalId( MyEntity.class ).load( "base" );
@ -61,28 +63,7 @@ public class InheritedNaturalIdCacheTest {
}
);
// finally, attempt to load MyEntity#1 as an ExtendedEntity, which should
// throw a WrongClassException
scope.inTransaction(
(session) -> {
try {
session.bySimpleNaturalId( ExtendedEntity.class ).load( "base" );
fail( "Expecting WrongClassException" );
}
catch (WrongClassException expected) {
// expected outcome
}
catch (Exception other) {
throw new AssertionError(
"Unexpected exception type : " + other.getClass().getName(),
other
);
}
}
);
// this is functionally equivalent to loading the wrong class by id...
// baseline: loading the wrong class by id...
scope.inTransaction(
(session) -> {
@ -102,6 +83,28 @@ public class InheritedNaturalIdCacheTest {
}
);
// finally, attempt to load MyEntity#1 as an ExtendedEntity, which should
// throw a `WrongClassException` same as above with loading by id
//
// NOTE : this is what fails currently
scope.inTransaction(
(session) -> {
try {
session.bySimpleNaturalId( ExtendedEntity.class ).load( "base" );
fail( "Expecting WrongClassException" );
}
catch (WrongClassException expected) {
// expected outcome
}
catch (Exception other) {
throw new AssertionError(
"Unexpected exception type : " + other.getClass().getName(),
other
);
}
}
);
}
}

View File

@ -7,6 +7,8 @@
package org.hibernate.orm.test.mapping.naturalid.mutable.cached;
import org.hibernate.LockOptions;
import org.hibernate.cache.spi.CacheImplementor;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.NotImplementedYet;
@ -36,6 +38,9 @@ public abstract class CachedMutableNaturalIdTest {
session.createQuery( "delete from A" ).executeUpdate();
}
);
final CacheImplementor cache = scope.getSessionFactory().getCache();
cache.evictAllRegions();
}
@Test
@ -94,8 +99,9 @@ public abstract class CachedMutableNaturalIdTest {
}
@Test
@NotImplementedYet( reason = "Caching is not yet implemented", strict = false )
public void testNaturalIdReCachingWhenNeeded(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
final Integer id = scope.fromTransaction(
(session) -> {
@ -119,12 +125,12 @@ public abstract class CachedMutableNaturalIdTest {
(session) -> {
final Another shouldBeGone = session.bySimpleNaturalId(Another.class).load("it");
assertNull( shouldBeGone );
assertEquals( 0, session.getSessionFactory().getStatistics().getNaturalIdCacheHitCount() );
assertEquals( 0, statistics.getNaturalIdCacheHitCount() );
}
);
// finally there should be only 2 NaturalIdCache puts : 1. insertion, 2. when updating natural-id from 'it' to 'name9'
assertEquals( 2, scope.getSessionFactory().getStatistics().getNaturalIdCachePutCount() );
// finally there should be only 2 NaturalIdCache puts : 1. insertion, 2. when updating natural-id from 'it' to 'it2'
assertEquals( 2, statistics.getNaturalIdCachePutCount() );
}
@Test

View File

@ -32,6 +32,8 @@ log4j.logger.org.hibernate.orm.query.sqm=debug
log4j.logger.org.hibernate.orm.query.hql=trace
log4j.logger.org.hibernate.sql.results.graph.DomainResultGraphPrinter=debug
log4j.logger.org.hibernate.orm.mapping.natural_id=debug
log4j.logger.org.hibernate.tool.hbm2ddl=trace
log4j.logger.org.hibernate.testing.cache=debug