diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java index 69afe56f1c22..da35766ef20f 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityDeleteAction.java @@ -60,16 +60,6 @@ public EntityDeleteAction( this.version = version; this.isCascadeDeleteEnabled = isCascadeDeleteEnabled; this.state = state; - - final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping(); - if ( naturalIdMapping != null ) { - naturalIdValues = session.getPersistenceContextInternal().getNaturalIdResolutions() - .removeLocalResolution( - getId(), - naturalIdMapping.extractNaturalIdFromEntityState( state ), - getPersister() - ); - } } /** @@ -125,6 +115,16 @@ public void execute() throws HibernateException { final boolean veto = isInstanceLoaded() && preDelete(); + final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping(); + if ( naturalIdMapping != null ) { + naturalIdValues = session.getPersistenceContextInternal().getNaturalIdResolutions() + .removeLocalResolution( + getId(), + naturalIdMapping.extractNaturalIdFromEntityState( state ), + getPersister() + ); + } + final Object ck = lockCacheItem(); if ( !isCascadeDeleteEnabled && !veto ) { diff --git a/hibernate-core/src/main/java/org/hibernate/generator/OnExecutionGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/OnExecutionGenerator.java index 44bb200b291c..12bddc28f3d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/OnExecutionGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/OnExecutionGenerator.java @@ -14,7 +14,7 @@ import org.hibernate.persister.entity.EntityPersister; import static org.hibernate.generator.EventType.INSERT; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql; /** diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java deleted file mode 100644 index 334cba4326aa..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.generator.internal; - -import org.hibernate.id.IdentifierGenerationException; -import org.hibernate.persister.entity.EntityPersister; - -/** - * @author Gavin King - */ -public class NaturalIdHelper { - public static String[] getNaturalIdPropertyNames(EntityPersister persister) { - final int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); - if ( naturalIdPropertyIndices == null ) { - throw new IdentifierGenerationException( "Entity '" + persister.getEntityName() - + "' has no '@NaturalId' property" ); - } - if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) { - throw new IdentifierGenerationException( "Entity '" + persister.getEntityName() - + "' has a '@NaturalId' property which is also defined as insert-generated" ); - } - final String[] allPropertyNames = persister.getPropertyNames(); - final String[] propertyNames = new String[naturalIdPropertyIndices.length]; - for ( int i = 0; i < naturalIdPropertyIndices.length; i++ ) { - propertyNames[i] = allPropertyNames[naturalIdPropertyIndices[i]]; - } - return propertyNames; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/generator/values/internal/GeneratedValuesHelper.java b/hibernate-core/src/main/java/org/hibernate/generator/values/internal/GeneratedValuesHelper.java index 981875f4ba13..513d529ecfd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/values/internal/GeneratedValuesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/values/internal/GeneratedValuesHelper.java @@ -52,7 +52,7 @@ import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.RowReader; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; import static org.hibernate.pretty.MessageHelper.infoString; /** diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java index 9846b43c0b54..b6bfb1d519ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentityGenerator.java @@ -18,7 +18,7 @@ import org.hibernate.persister.entity.EntityPersister; import static org.hibernate.generator.EventType.INSERT; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql; /** diff --git a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java index 35fc525788ee..bea88b426842 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java @@ -11,7 +11,7 @@ import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.persister.entity.EntityPersister; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; /** * A generator that {@code select}s the just-{@code insert}ed row to determine the diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java new file mode 100644 index 000000000000..df68edbdfd2c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdHelper.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.internal; + +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.id.IdentifierGenerationException; +import org.hibernate.loader.LoaderLogging; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.persister.entity.EntityPersister; + +import java.util.Collection; + +/** + * @author Gavin King + */ +public class NaturalIdHelper { + public static String[] getNaturalIdPropertyNames(EntityPersister persister) { + final int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); + if ( naturalIdPropertyIndices == null ) { + throw new IdentifierGenerationException( "Entity '" + persister.getEntityName() + + "' has no '@NaturalId' property" ); + } + if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) { + throw new IdentifierGenerationException( "Entity '" + persister.getEntityName() + + "' has a '@NaturalId' property which is also defined as insert-generated" ); + } + final String[] allPropertyNames = persister.getPropertyNames(); + final String[] propertyNames = new String[naturalIdPropertyIndices.length]; + for ( int i = 0; i < naturalIdPropertyIndices.length; i++ ) { + propertyNames[i] = allPropertyNames[naturalIdPropertyIndices[i]]; + } + return propertyNames; + } + + public static void performAnyNeededCrossReferenceSynchronizations( + boolean synchronizationEnabled, + EntityMappingType entityMappingType, + SharedSessionContractImplementor session) { + + if ( !synchronizationEnabled ) { + // synchronization (this process) was disabled + return; + } + + final NaturalIdMapping naturalIdMapping = entityMappingType.getNaturalIdMapping(); + + if ( !naturalIdMapping.isMutable() ) { + // only mutable natural-ids need this processing + return; + } + + if ( ! session.isTransactionInProgress() ) { + // not in a transaction so skip synchronization + return; + } + + final EntityPersister entityPersister = entityMappingType.getEntityPersister(); + + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final Collection cachedPkResolutions = + persistenceContext.getNaturalIdResolutions() + .getCachedPkResolutions( entityPersister ); + final boolean loggerDebugEnabled = LoaderLogging.LOADER_LOGGER.isDebugEnabled(); + for ( Object pk : cachedPkResolutions ) { + final EntityKey entityKey = session.generateEntityKey( pk, entityPersister ); + final Object entity = persistenceContext.getEntity( entityKey ); + final EntityEntry entry = persistenceContext.getEntry( entity ); + + if ( entry == null ) { + if ( loggerDebugEnabled ) { + LoaderLogging.LOADER_LOGGER.debugf( + "Cached natural-id/pk resolution linked to null EntityEntry in persistence context : %s#%s", + entityMappingType.getEntityName(), + pk + ); + } + continue; + } + + if ( !entry.requiresDirtyCheck( entity ) ) { + continue; + } + + // MANAGED is the only status we care about here... + if ( entry.getStatus() != Status.MANAGED ) { + continue; + } + + persistenceContext.getNaturalIdResolutions().handleSynchronization( pk, entity, entityPersister ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java index 645879e57ffa..640f23a504ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java @@ -5,7 +5,6 @@ package org.hibernate.internal; import java.util.List; -import java.util.Set; import org.hibernate.CacheMode; import org.hibernate.LockOptions; @@ -16,10 +15,11 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions; import org.hibernate.persister.entity.EntityPersister; +import static org.hibernate.internal.NaturalIdHelper.performAnyNeededCrossReferenceSynchronizations; + /** * @author Steve Ebersole */ @@ -82,6 +82,8 @@ public NaturalIdMultiLoadAccess enableOrderedReturn(boolean enabled) { @Override @SuppressWarnings( "unchecked" ) public List multiLoad(Object... ids) { + performAnyNeededCrossReferenceSynchronizations( true, entityDescriptor, session ); + final CacheMode sessionCacheMode = session.getCacheMode(); boolean cacheModeChanged = false; @@ -110,7 +112,6 @@ public List multiLoad(Object... ids) { } try { - session.autoFlushIfRequired( (Set) CollectionHelper.setOf( entityDescriptor.getQuerySpaces() ) ); return (List) entityDescriptor.getMultiNaturalIdLoader().multiLoad( ids, this, session ); } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java index 26ce6bb49c27..3cbe665c8184 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiNaturalIdLoader.java @@ -14,10 +14,13 @@ import org.hibernate.loader.ast.spi.MultiNaturalIdLoader; import org.hibernate.metamodel.mapping.EntityMappingType; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; +import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; +import static org.hibernate.loader.ast.internal.LoaderHelper.upgradeLock; + /** * @author Jan Schatteman */ @@ -57,7 +60,18 @@ private List performUnorderedMultiLoad(K[] naturalIds, MultiNaturalIdLoad ); } - protected abstract List unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions); + protected List unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) { + final List results = arrayList( naturalIds.length ); + final Object[] unresolvedIds = + checkPersistenceContextForCachedResults( naturalIds, session, lockOptions, results ); + if ( !isEmpty( unresolvedIds ) ) { + results.addAll( loadEntitiesWithUnresolvedIds(unresolvedIds, session, lockOptions) ); + } + + return results; + } + + protected abstract List loadEntitiesWithUnresolvedIds(Object[] unresolvedIds, SharedSessionContractImplementor session, LockOptions lockOptions); private List performOrderedMultiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) { if ( MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { @@ -75,19 +89,18 @@ protected List orderedMultiLoad( K[] naturalIds, SharedSessionContractImp unorderedMultiLoad( naturalIds, session, lockOptions ); - return handleResults( naturalIds, session, lockOptions ); + return sortResults( naturalIds, session, lockOptions ); } - protected List handleResults( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) { - List results = new ArrayList<>(naturalIds.length); + protected List sortResults( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) { + List results = arrayList(naturalIds.length); for ( int i = 0; i < naturalIds.length; i++ ) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); Object id = persistenceContext.getNaturalIdResolutions().findCachedIdByNaturalId( naturalIds[i], getEntityDescriptor() ); // Id can be null if a non-existent natural id is requested - Object entity = id == null ? null - : persistenceContext.getEntity( new EntityKey( id, getEntityDescriptor().getEntityPersister() ) ); + Object entity = (id == null) ? null : persistenceContext.getEntity( new EntityKey( id, getEntityDescriptor().getEntityPersister() ) ); if ( entity != null && !options.isReturnOfDeletedEntitiesEnabled() ) { // make sure it is not DELETED final EntityEntry entry = persistenceContext.getEntry( entity ); @@ -105,6 +118,38 @@ protected List handleResults( K[] naturalIds, SharedSessionContractImplem return results; } + private Object[] checkPersistenceContextForCachedResults( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions, List results ) { + List unresolvedIds = arrayList(naturalIds.length); + + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + for ( int i = 0; i < naturalIds.length; i++ ) { + + final Object normalizedNaturalId = getEntityDescriptor().getNaturalIdMapping().normalizeInput( naturalIds[i] ); + Object id = persistenceContext.getNaturalIdResolutions().findCachedIdByNaturalId( normalizedNaturalId, getEntityDescriptor() ); + + // Id can be null if a non-existent natural id is requested, or a mutable natural id was changed and then deleted + Object entity = id == null ? null : persistenceContext.getEntity( new EntityKey( id, getEntityDescriptor().getEntityPersister() ) ); + + if ( entity != null ) { + // Entity is already in the persistence context + final EntityEntry entry = persistenceContext.getEntry( entity ); + // either a managed entry, or a deleted one with returnDeleted enabled + if ( !entry.getStatus().isDeletedOrGone() || options.isReturnOfDeletedEntitiesEnabled() ) { + results.add( (E) persistenceContext.proxyFor(entity) ); + upgradeLock( entity, entry, lockOptions, session.getSession().asEventSource() ); + } + } + else { + // entity either doesn't exist or hasn't been loaded in the PC yet, in both cases we add the natural id + // to the ids that still need to be recovered, in case the id corresponds to a non-existent + // instance nothing will be in the results for it, which is ok in unordered multiload + unresolvedIds.add(naturalIds[i]); + } + } + + return unresolvedIds.toArray( new Object[0] ); + } + @Override public EntityMappingType getLoadable() { return getEntityDescriptor(); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderArrayParam.java index 3a9f5258d5d5..25364be9b859 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderArrayParam.java @@ -44,7 +44,7 @@ protected BasicAttributeMapping getNaturalIdAttribute() { } @Override - public List unorderedMultiLoad( K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions ) { + public List loadEntitiesWithUnresolvedIds(Object[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) { final SessionFactoryImplementor sessionFactory = session.getFactory(); @@ -77,17 +77,17 @@ public LockOptions getLockOptions() { } ); return LoaderHelper.loadByArrayParameter( - naturalIds, - sqlAst, - jdbcSelectOperation, - jdbcParameter, - arrayJdbcMapping, - null, - null, - null, - lockOptions, - session.isDefaultReadOnly(), - session + naturalIds, + sqlAst, + jdbcSelectOperation, + jdbcParameter, + arrayJdbcMapping, + null, + null, + null, + lockOptions, + session.isDefaultReadOnly(), + session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderInPredicate.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderInPredicate.java index 262bdec8609b..cdf2e9e07c3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderInPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoaderInPredicate.java @@ -22,7 +22,7 @@ public MultiNaturalIdLoaderInPredicate(EntityMappingType entityDescriptor) { } @Override - public List unorderedMultiLoad(K[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) { + public List loadEntitiesWithUnresolvedIds(Object[] naturalIds, SharedSessionContractImplementor session, LockOptions lockOptions) { final SessionFactoryImplementor sessionFactory = session.getFactory(); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java index 20829384370d..576e63faf19c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/BaseNaturalIdLoadAccessImpl.java @@ -4,7 +4,6 @@ */ package org.hibernate.loader.internal; -import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -14,7 +13,6 @@ import org.hibernate.UnknownProfileException; import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; @@ -25,12 +23,12 @@ import org.hibernate.loader.LoaderLogging; import org.hibernate.loader.ast.spi.NaturalIdLoadOptions; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE; +import static org.hibernate.internal.NaturalIdHelper.performAnyNeededCrossReferenceSynchronizations; /** * Base support for load-by-natural-id @@ -121,63 +119,9 @@ protected void synchronizationEnabled(boolean synchronizationEnabled) { // : resolvedId; // } - protected void performAnyNeededCrossReferenceSynchronizations() { - if ( !synchronizationEnabled ) { - // synchronization (this process) was disabled - return; - } - - final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping(); - - if ( !naturalIdMapping.isMutable() ) { - // only mutable natural-ids need this processing - return; - } - - final SessionImplementor session = context.getSession(); - - if ( ! session.isTransactionInProgress() ) { - // not in a transaction so skip synchronization - return; - } - - final PersistenceContext persistenceContext = context.getSession().getPersistenceContextInternal(); - final Collection cachedPkResolutions = - persistenceContext.getNaturalIdResolutions() - .getCachedPkResolutions( entityPersister() ); - final boolean loggerDebugEnabled = LoaderLogging.LOADER_LOGGER.isDebugEnabled(); - for ( Object pk : cachedPkResolutions ) { - final EntityKey entityKey = context.getSession().generateEntityKey( pk, entityPersister() ); - final Object entity = persistenceContext.getEntity( entityKey ); - final EntityEntry entry = persistenceContext.getEntry( entity ); - - if ( entry == null ) { - if ( loggerDebugEnabled ) { - LoaderLogging.LOADER_LOGGER.debugf( - "Cached natural-id/pk resolution linked to null EntityEntry in persistence context : %s#%s", - entityDescriptor.getEntityName(), - pk - ); - } - continue; - } - - if ( !entry.requiresDirtyCheck( entity ) ) { - continue; - } - - // MANAGED is the only status we care about here... - if ( entry.getStatus() != Status.MANAGED ) { - continue; - } - - persistenceContext.getNaturalIdResolutions().handleSynchronization( pk, entity, entityPersister() ); - } - } - @SuppressWarnings( "unchecked" ) protected final T doGetReference(Object normalizedNaturalIdValue) { - performAnyNeededCrossReferenceSynchronizations(); + performAnyNeededCrossReferenceSynchronizations( synchronizationEnabled, entityDescriptor, context.getSession() ); context.checkOpenOrWaitingForAutoClose(); context.pulseTransactionCoordinator(); @@ -212,7 +156,7 @@ protected final T doGetReference(Object normalizedNaturalIdValue) { @SuppressWarnings("unchecked") protected final T doLoad(Object normalizedNaturalIdValue) { - performAnyNeededCrossReferenceSynchronizations(); + performAnyNeededCrossReferenceSynchronizations( synchronizationEnabled, entityDescriptor, context.getSession() ); context.checkOpenOrWaitingForAutoClose(); context.pulseTransactionCoordinator(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java index a27942227f8c..03e262564199 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java @@ -33,6 +33,7 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -165,7 +166,7 @@ void testMultiLoadSimpleIdEntityPessimisticReadLock(SessionFactoryScope scope) { assertNotNull(customersLoaded); assertEquals(customerList.size(), customersLoaded.size()); customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test findMultiple scope.inTransaction( session -> { @@ -173,7 +174,7 @@ void testMultiLoadSimpleIdEntityPessimisticReadLock(SessionFactoryScope scope) { assertNotNull(customersLoaded); assertEquals(customerList.size(), customersLoaded.size()); customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test byMultipleNaturalId scope.inTransaction( session -> { @@ -183,7 +184,7 @@ void testMultiLoadSimpleIdEntityPessimisticReadLock(SessionFactoryScope scope) { assertNotNull(customersLoaded); assertEquals(customerList.size(), customersLoaded.size()); customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); } @@ -206,7 +207,7 @@ void testMultiLoadCompositeIdEntityPessimisticReadLockAlreadyInSession(SessionFa assertNotNull(entitiesLoaded); assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test findMultiple scope.inTransaction( session -> { @@ -220,7 +221,7 @@ void testMultiLoadCompositeIdEntityPessimisticReadLockAlreadyInSession(SessionFa assertNotNull(entitiesLoaded); assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test byMultipleNaturalId scope.inTransaction( session -> { @@ -235,12 +236,16 @@ void testMultiLoadCompositeIdEntityPessimisticReadLockAlreadyInSession(SessionFa assertNotNull(entitiesLoaded); assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); - checkStatement( lockString ); + // HHH-19248: multi natural-id loading checks the session (and upgrades locks) so 2 statements are expected + checkStatement( 2, lockString ); } ); } // (3) simple Id entity w/ pessimistic write lock (one in L1C & some in L2C) @Test + @SkipForDialect( dialectClass = PostgreSQLDialect.class, matchSubTypes = true, + reason = "Excluding PostgreSQL dialects for now; multiload of natural id entities where one is already " + + "in the session produces more than 1 select, and they have different update lock strings") public void testMultiLoadSimpleIdEntityPessimisticWriteLockSomeInL1CAndSomeInL2C(SessionFactoryScope scope) { final Integer userInL2CId = userIds.get(0); final Integer userInL1CId = userIds.get(1); @@ -272,7 +277,7 @@ public void testMultiLoadSimpleIdEntityPessimisticWriteLockSomeInL1CAndSomeInL2C assertNotNull(usersLoaded); assertEquals(userList.size(), usersLoaded.size()); usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test findMultiple scope.inTransaction( session -> { @@ -284,7 +289,7 @@ public void testMultiLoadSimpleIdEntityPessimisticWriteLockSomeInL1CAndSomeInL2C assertNotNull(usersLoaded); assertEquals(userList.size(), usersLoaded.size()); usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); - checkStatement( lockString ); + checkStatement( 1, lockString ); } ); // test byMultipleNaturalId scope.inTransaction( session -> { @@ -298,7 +303,8 @@ public void testMultiLoadSimpleIdEntityPessimisticWriteLockSomeInL1CAndSomeInL2C assertNotNull(usersLoaded); assertEquals(userList.size(), usersLoaded.size()); usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); - checkStatement( lockString ); + // HHH-19248: multi natural-id loading checks the session (and upgrades locks) so 2 statements are expected + checkStatement( 2,lockString ); } ); } @@ -363,9 +369,11 @@ void testMultiLoadSimpleIdEntityOptimisticForceIncrementLock(SessionFactoryScope } ); } - private void checkStatement(String lockString) { - assertEquals( 1,sqlStatementInspector.getSqlQueries().size() ); - assertTrue( sqlStatementInspector.getSqlQueries().get( 0 ).contains( lockString ) ); + private void checkStatement(int stmtCount, String lockString) { + assertEquals( stmtCount,sqlStatementInspector.getSqlQueries().size() ); + for ( String stmt : sqlStatementInspector.getSqlQueries() ) { + assertTrue( stmt.contains( lockString ) ); + } sqlStatementInspector.clear(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiNaturalIdLoadTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiNaturalIdLoadTest.java index 31074cbf7a79..63f396ec9ac9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiNaturalIdLoadTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiNaturalIdLoadTest.java @@ -16,11 +16,9 @@ import org.hibernate.loader.ast.internal.MultiKeyLoadHelper; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,7 +34,11 @@ * @author Jan Schatteman */ @DomainModel( - annotatedClasses = { MultiNaturalIdLoadTest.SimpleNaturalIdEntity.class, MultiNaturalIdLoadTest.CompositeNaturalIdEntity.class } + annotatedClasses = { + MultiNaturalIdLoadTest.SimpleNaturalIdEntity.class, + MultiNaturalIdLoadTest.SimpleMutableNaturalIdEntity.class, + MultiNaturalIdLoadTest.CompositeNaturalIdEntity.class + } ) @SessionFactory( useCollectingStatementInspector = true ) class MultiNaturalIdLoadTest { @@ -51,6 +53,9 @@ public void setup(SessionFactoryScope scope) { for ( int i = 1; i <= 10; i++ ) { session.persist( new SimpleNaturalIdEntity( i, "Entity" + i ) ); } + for ( int i = 1; i <= 10; i++ ) { + session.persist( new SimpleMutableNaturalIdEntity( i, "MIdEntity" + i ) ); + } for ( int i = 1; i <= 10; i++ ) { session.persist( new CompositeNaturalIdEntity( i, "Entity" + i, i + "Entity" ) ); } @@ -63,6 +68,7 @@ public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> { session.createMutationQuery( "delete SimpleNaturalIdEntity" ).executeUpdate(); + session.createMutationQuery( "delete SimpleMutableNaturalIdEntity" ).executeUpdate(); session.createMutationQuery( "delete CompositeNaturalIdEntity" ).executeUpdate(); } ); @@ -221,7 +227,7 @@ public void testNonExistentIdRequest(SessionFactoryScope scope) { assertEquals( 3, list.size() ); assertNull( list.get( 1 ) ); - // un-ordered multiLoad + // unordered multiLoad list = session.byMultipleNaturalId( SimpleNaturalIdEntity.class ).enableOrderedReturn( false ).multiLoad( "Entity4","Entity99","Entity5" ); assertEquals( 2, list.size() ); } @@ -229,38 +235,44 @@ public void testNonExistentIdRequest(SessionFactoryScope scope) { } @Test - public void testUnflushedDeleteAndThenOrderedMultiLoadSimpleIds(SessionFactoryScope scope) { + public void testOrderedDuplicatedRequestedSimpleIds(SessionFactoryScope scope) { scope.inTransaction( session -> { - // delete one of them (but do not flush)... - SimpleNaturalIdEntity se = session.getReference( SimpleNaturalIdEntity.class, 2 ); - Hibernate.initialize( se ); - session.remove( se ); + // ordered multiLoad + List ids = List.of("Entity4","Entity2","Entity4","Entity5","Entity4"); + List results = session.byMultipleNaturalId( SimpleNaturalIdEntity.class ).multiLoad( ids ); + assertEquals( 5, results.size() ); + assertEquals( results.get( 0 ), results.get( 2 ) ); + assertEquals( results.get( 0 ), results.get( 4 ) ); + } + ); + } - // finally assert how multiLoad handles it - List ids = List.of("Entity4","Entity2","Entity5"); + @Test + public void testUnorderedDuplicatedRequestedSimpleIds(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // un-ordered multiLoad + List ids = List.of("Entity4","Entity2","Entity4","Entity5","Entity4"); List results = session .byMultipleNaturalId( SimpleNaturalIdEntity.class ) + .enableOrderedReturn( false ) .multiLoad( ids ); assertEquals( 3, results.size() ); - assertEquals(ids.get(0), results.get(0).getSsn() ); - Assertions.assertNull( results.get(1) ); - assertEquals(ids.get(2), results.get(2).getSsn() ); } ); } @Test - @FailureExpected(jiraKey = "HHH-19248", reason = "should return 3 entities similarly to the multiloadtest with return delete enabled, but returns only 2") - public void testUnflushedDeleteAndThenOrderedMultiLoadSimpleIdsWithReturnDeletedEnabled(SessionFactoryScope scope) { + public void testUnflushedDeleteAndThenOrderedMultiLoadSimpleNonmutableIdsWithReturnDeletedEnabled(SessionFactoryScope scope) { scope.inTransaction( session -> { // delete one of them (but do not flush)... - SimpleNaturalIdEntity se = session.getReference( SimpleNaturalIdEntity.class, 2 ); + SimpleNaturalIdEntity se = session.find( SimpleNaturalIdEntity.class, 2 ); Hibernate.initialize( se ); session.remove( se ); - // finally assert how multiLoad handles it + // with enableReturnOfDeletedEntities set to true, multiLoad should return 3 entities (no nulls) List ids = List.of("Entity4","Entity2","Entity5"); List results = session .byMultipleNaturalId( SimpleNaturalIdEntity.class ) @@ -275,8 +287,7 @@ public void testUnflushedDeleteAndThenOrderedMultiLoadSimpleIdsWithReturnDeleted } @Test - @FailureExpected(jiraKey = "HHH-19248", reason = "should return 3 entities similarly to the multiloadtest with return delete enabled, but returns only 2") - public void testUnflushedDeleteAndThenUnorderedMultiLoadSimpleIds(SessionFactoryScope scope) { + public void testUnflushedDeleteAndThenOrderedMultiLoadSimpleNonmutableIdsWithReturnDeletedDisabled(SessionFactoryScope scope) { scope.inTransaction( session -> { // delete one of them (but do not flush)... @@ -288,42 +299,265 @@ public void testUnflushedDeleteAndThenUnorderedMultiLoadSimpleIds(SessionFactory List ids = List.of("Entity4","Entity2","Entity5"); List results = session .byMultipleNaturalId( SimpleNaturalIdEntity.class ) + .enableReturnOfDeletedEntities( false ) + .multiLoad( ids ); + assertEquals( 3, results.size() ); + assertEquals(ids.get(0), results.get(0).getSsn() ); + assertNull(results.get(1)); + assertEquals(ids.get(2), results.get(2).getSsn() ); + } + ); + } + + @Test + public void testOrderedMultiLoadSimpleIds1(SessionFactoryScope scope) { + // mutate one of the natural ids (but do not flush) + scope.inTransaction( + session -> { + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .multiLoad( ids ); + assertEquals( 3, results.size() ); + assertEquals( ids.get( 0 ), results.get( 0 ).getSsn() ); + assertEquals( ids.get( 1 ), results.get( 1 ).getSsn() ); + assertEquals( ids.get( 2 ), results.get( 2 ).getSsn() ); + } + ); + } + + @Test + public void testOrderedMultiLoadSimpleIds2(SessionFactoryScope scope) { + // mutate and delete one of the natural ids (but do not flush) + // don't return deleted instances + scope.inTransaction( + session -> { + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( sme ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .multiLoad( ids ); + assertEquals( 3, results.size() ); + assertEquals( ids.get( 0 ), results.get( 0 ).getSsn() ); + assertNull( results.get( 1 ) ); + assertEquals( ids.get( 2 ), results.get( 2 ).getSsn() ); + } + ); + } + + @Test + public void testOrderedMultiLoadSimpleIds3(SessionFactoryScope scope) { + // mutate and delete one of the natural ids (but do not flush) + // return deleted instances + scope.inTransaction( + session -> { + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( sme ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .enableReturnOfDeletedEntities( true ) + .multiLoad( ids ); + // the deleted instance is still not returned, since the natural id synchronisation only happens + // on managed entities, so in this case the PC simply isn't aware of the mutated id instance + assertEquals( 3, results.size() ); + assertEquals( ids.get( 0 ), results.get( 0 ).getSsn() ); + assertNull( results.get( 1 ) ); + assertEquals( ids.get( 2 ), results.get( 2 ).getSsn() ); + } + ); + } + + @Test + public void testOrderedMultiLoadSimpleIds4(SessionFactoryScope scope) { + // mutate and delete another one of the natural ids (but do not flush)... + // don't return deleted instances + scope.inTransaction( + session -> { + // mutate one of the natural ids and delete another instance (but do not flush)... + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( session.find( SimpleMutableNaturalIdEntity.class, 5 ) ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .multiLoad( ids ); + assertEquals( 3, results.size() ); + assertEquals( ids.get( 0 ), results.get( 0 ).getSsn() ); + assertEquals( ids.get( 1 ), results.get( 1 ).getSsn() ); + assertNull( results.get( 2 ) ); + } + ); + } + + @Test + public void testOrderedMultiLoadSimpleIds5(SessionFactoryScope scope) { + // mutate and delete another one of the natural ids (but do not flush)... + // return deleted instances + scope.inTransaction( + session -> { + // mutate one of the natural ids and delete another instance (but do not flush)... + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( session.find( SimpleMutableNaturalIdEntity.class, 5 ) ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .enableReturnOfDeletedEntities( true ) + .multiLoad( ids ); + assertEquals( 3, results.size() ); + assertEquals( ids.get( 0 ), results.get( 0 ).getSsn() ); + assertEquals( ids.get( 1 ), results.get( 1 ).getSsn() ); + assertEquals( ids.get( 2 ), results.get( 2 ).getSsn() ); + } + ); + } + + @Test + public void testUnorderedMultiLoadSimpleIds1(SessionFactoryScope scope) { + // mutate one of the natural ids (but do not flush) + scope.inTransaction( + session -> { + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) .enableOrderedReturn( false ) .multiLoad( ids ); assertEquals( 3, results.size() ); + verifyUnorderedResult( ids, results ); } ); } @Test - public void testOrderedDuplicatedRequestedSimpleIds(SessionFactoryScope scope) { + public void testUnorderedMultiLoadSimpleIds2(SessionFactoryScope scope) { + // mutate and delete one of the natural ids (but do not flush) + // don't return deleted instances scope.inTransaction( session -> { - // ordered multiLoad - List ids = List.of("Entity4","Entity2","Entity4","Entity5","Entity4"); - List results = session.byMultipleNaturalId( SimpleNaturalIdEntity.class ).multiLoad( ids ); - assertEquals( 5, results.size() ); - assertEquals( results.get( 0 ), results.get( 2 ) ); - assertEquals( results.get( 0 ), results.get( 4 ) ); + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( sme ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .enableOrderedReturn( false ) + .multiLoad( ids ); + assertEquals( 2, results.size() ); + verifyUnorderedResult( List.of( "MIdEntity4", "MIdEntity5" ), results ); } ); } @Test - public void testUnorderedDuplicatedRequestedSimpleIds(SessionFactoryScope scope) { + public void testUnorderedMultiLoadSimpleIds3(SessionFactoryScope scope) { + // mutate and delete one of the natural ids (but do not flush) + // return deleted instances scope.inTransaction( session -> { - // un-ordered multiLoad - List ids = List.of("Entity4","Entity2","Entity4","Entity5","Entity4"); - List results = session - .byMultipleNaturalId( SimpleNaturalIdEntity.class ) + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( sme ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .enableOrderedReturn( false ) + .enableReturnOfDeletedEntities( true ) + .multiLoad( ids ); + // the deleted instance is still not returned, since the natural id synchronisation only happens + // on managed entities, so in this case the PC simply isn't aware of the mutated id instance + assertEquals( 2, results.size() ); + verifyUnorderedResult( List.of( "MIdEntity4", "MIdEntity5" ), results ); + } + ); + } + + @Test + public void testUnorderedMultiLoadSimpleIds4(SessionFactoryScope scope) { + // mutate and delete another one of the natural ids (but do not flush)... + // don't return deleted instances + scope.inTransaction( + session -> { + // mutate one of the natural ids and delete another instance (but do not flush)... + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( session.find( SimpleMutableNaturalIdEntity.class, 5 ) ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) + .enableOrderedReturn( false ) + .multiLoad( ids ); + assertEquals( 2, results.size() ); + verifyUnorderedResult( List.of( "MIdEntity4", "MIdEntity22" ), results ); + } + ); + } + + @Test + public void testUnorderedMultiLoadSimpleIds5(SessionFactoryScope scope) { + // mutate and delete another one of the natural ids (but do not flush)... + // return deleted instances + scope.inTransaction( + session -> { + // mutate one of the natural ids and delete another instance (but do not flush)... + SimpleMutableNaturalIdEntity sme = session.find( SimpleMutableNaturalIdEntity.class, 2 ); + sme.setSsn( "MIdEntity22" ); + session.remove( session.find( SimpleMutableNaturalIdEntity.class, 5 ) ); + + List ids = List.of( "MIdEntity4", "MIdEntity22", "MIdEntity5" ); + + List results = session + .byMultipleNaturalId( SimpleMutableNaturalIdEntity.class ) .enableOrderedReturn( false ) + .enableReturnOfDeletedEntities( true ) .multiLoad( ids ); assertEquals( 3, results.size() ); + verifyUnorderedResult( ids, results ); } ); } + private void verifyUnorderedResult(List ids, List results) { + int count = 0; + Iterator it = results.iterator(); + do { + SimpleMutableNaturalIdEntity sme = it.next(); + for ( String id : ids ) { + if ( id.equalsIgnoreCase( sme.getSsn() ) ) { + it.remove(); + count++; + } + } + } while ( it.hasNext() ); + assertEquals( 0, results.size() ); + assertEquals( ids.size(), count ); + } + +// private List removeNulls(List l) { +// return l.stream().filter( simpleMutableNaturalIdEntity -> simpleMutableNaturalIdEntity != null ).toList(); +// } + @Cacheable @Entity(name = "SimpleNaturalIdEntity") public static class SimpleNaturalIdEntity { @@ -357,6 +591,39 @@ public void setSsn(String ssn) { } } + @Cacheable + @Entity(name = "SimpleMutableNaturalIdEntity") + public static class SimpleMutableNaturalIdEntity { + @Id + Integer id; + @NaturalId(mutable = true) + String ssn; + + public SimpleMutableNaturalIdEntity() { + } + + public SimpleMutableNaturalIdEntity(Integer id, String ssn) { + this.id = id; + this.ssn = ssn; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSsn() { + return ssn; + } + + public void setSsn(String ssn) { + this.ssn = ssn; + } + } + @Entity(name = "CompositeNaturalIdEntity") public static class CompositeNaturalIdEntity { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/immutableentity/ImmutableEntityNaturalIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/immutableentity/ImmutableEntityNaturalIdTest.java index 77fdc479155e..a19762f91a1a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/immutableentity/ImmutableEntityNaturalIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/immutableentity/ImmutableEntityNaturalIdTest.java @@ -168,7 +168,7 @@ public void testImmutableNaturalIdLifecycle(SessionFactoryScope scope) { // third query naturalIdLoader.load(); assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() ); - assertEquals( "Cache misses should be two after second query", 2, stats.getNaturalIdCacheMissCount() ); + assertEquals( "Cache misses should be two after second query", 1, stats.getNaturalIdCacheMissCount() ); assertEquals( "Cache put should be one after second query", 1, stats.getNaturalIdCachePutCount() ); } ); @@ -186,7 +186,7 @@ public void testImmutableNaturalIdLifecycle(SessionFactoryScope scope) { // second query assertNull( building ); assertEquals( "Cache hits should be one after third query", 1, stats.getNaturalIdCacheHitCount() ); - assertEquals( "Cache misses should be one after third query", 3, stats.getNaturalIdCacheMissCount() ); + assertEquals( "Cache misses should be one after third query", 2, stats.getNaturalIdCacheMissCount() ); assertEquals( "Cache put should be one after third query", 1, stats.getNaturalIdCachePutCount() ); // here, we should know that that natural-id does not exist as part of the Session... @@ -197,7 +197,7 @@ public void testImmutableNaturalIdLifecycle(SessionFactoryScope scope) { .load(); assertEquals( "Cache hits should still be one", 1, stats.getNaturalIdCacheHitCount() ); - assertEquals( "Cache misses should now be four", 4, stats.getNaturalIdCacheMissCount() ); + assertEquals( "Cache misses should now be four", 3, stats.getNaturalIdCacheMissCount() ); assertEquals( "Cache put should still be one", 1, stats.getNaturalIdCachePutCount() ); } );