diff --git a/hibernate-core/src/main/java/org/hibernate/IncludeRemovals.java b/hibernate-core/src/main/java/org/hibernate/IncludeRemovals.java new file mode 100644 index 000000000000..243225a1a3e5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/IncludeRemovals.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + + +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FindOption; + +import java.util.List; + +/** + * MultiFindOption implementation to specify whether the returned list + * of entity instances should contain instances that have been + * {@linkplain Session#remove(Object) marked for removal} in the + * current session, but not yet deleted in the database. + *

+ * The default is {@link #EXCLUDE}, meaning that instances marked for + * removal are replaced by null in the returned list of entities when {@link OrderedReturn} + * is used. + * + * @see org.hibernate.MultiFindOption + * @see OrderedReturn + * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + */ +public enum IncludeRemovals implements MultiFindOption { + INCLUDE, + EXCLUDE +} diff --git a/hibernate-core/src/main/java/org/hibernate/MultiFindOption.java b/hibernate-core/src/main/java/org/hibernate/MultiFindOption.java new file mode 100644 index 000000000000..400f7b9354ad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/MultiFindOption.java @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + + +import jakarta.persistence.FindOption; + +/** + * Simple marker interface for FindOptions which can be applied to multiple id loading. + */ +public interface MultiFindOption extends FindOption { +} diff --git a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java index 0d33cbff22db..2b5f850ff4b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java @@ -31,7 +31,11 @@ * @see Session#byMultipleIds(Class) * * @author Steve Ebersole + + * @deprecated Use forms of {@linkplain Session#findMultiple} accepting + * {@linkplain jakarta.persistence.FindOption} instead of {@linkplain Session#byMultipleIds}. */ +@Deprecated(since = "7.2", forRemoval = true) public interface MultiIdentifierLoadAccess { /** diff --git a/hibernate-core/src/main/java/org/hibernate/OrderedReturn.java b/hibernate-core/src/main/java/org/hibernate/OrderedReturn.java new file mode 100644 index 000000000000..5ba775c590ab --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/OrderedReturn.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + + +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FindOption; + +import java.util.List; + +/** + * MultiFindOption implementation to specify whether the returned list + * of entity instances should be ordered, where the position of an entity + * instance is determined by the position of its identifier + * in the list of ids passed to {@code findMultiple(...)}. + *

+ * The default is {@link #ORDERED}, meaning the positions of the entities + * in the returned list correspond to the positions of their ids. In this case, + * the {@link IncludeRemovals} handling of entities marked for removal + * becomes important. + * + * @see org.hibernate.MultiFindOption + * @see IncludeRemovals + * @see org.hibernate.Session#findMultiple(Class, List, FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + */ +public enum OrderedReturn implements MultiFindOption { + ORDERED, + UNORDERED +} diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index abc2c2f3eec6..8d925c331240 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -1165,7 +1165,12 @@ public interface Session extends SharedSessionContract, EntityManager { * @throws HibernateException If the given class does not resolve as a mapped entity * * @see #findMultiple(Class, List, FindOption...) + * + * @deprecated This method will be removed. + * Use {@link #findMultiple(Class, List, FindOption...)} instead. + * See {@link MultiFindOption}. */ + @Deprecated(since = "7.2", forRemoval = true) MultiIdentifierLoadAccess byMultipleIds(Class entityClass); /** @@ -1177,7 +1182,12 @@ public interface Session extends SharedSessionContract, EntityManager { * @return an instance of {@link MultiIdentifierLoadAccess} for executing the lookup * * @throws HibernateException If the given name does not resolve to a mapped entity + * + * @deprecated This method will be removed. + * Use {@link #findMultiple(Class, List, FindOption...)} instead. + * See {@link MultiFindOption}. */ + @Deprecated(since = "7.2", forRemoval = true) MultiIdentifierLoadAccess byMultipleIds(String entityName); /** diff --git a/hibernate-core/src/main/java/org/hibernate/SessionChecking.java b/hibernate-core/src/main/java/org/hibernate/SessionChecking.java new file mode 100644 index 000000000000..37757af3655d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/SessionChecking.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + + +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FindOption; + +import java.util.List; + +/** + * MultiFindOption implementation to specify whether the ids of managed entity instances already + * cached in the current persistence context should be excluded. + * from the list of ids sent to the database. + *

+ * The default is {@link #DISABLED}, meaning all ids are included and sent to the database. + * + * Use {@link #ENABLED} to exclude already managed entity instance ids from + * the list of ids sent to the database. + * + * @see org.hibernate.MultiFindOption + * @see org.hibernate.Session#findMultiple(Class, List , FindOption...) + * @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...) + */ +public enum SessionChecking implements MultiFindOption { + ENABLED, + DISABLED +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index c48197058fd7..550560ee94f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -964,6 +964,15 @@ else if ( option instanceof ReadOnlyMode ) { else if ( option instanceof BatchSize batchSizeOption ) { batchSize = batchSizeOption.batchSize(); } + else if ( option instanceof SessionChecking sessionChecking ) { + loadAccess.enableSessionCheck( option == sessionChecking.ENABLED ); + } + else if ( option instanceof OrderedReturn orderedReturn ) { + loadAccess.enableOrderedReturn( option == orderedReturn.ORDERED ); + } + else if ( option instanceof IncludeRemovals includeRemovals ) { + loadAccess.enableReturnOfDeletedEntities( option == includeRemovals.INCLUDE ); + } } loadAccess.with( lockOptions ) .with( interpretCacheMode( storeMode, retrieveMode ) ) @@ -2291,6 +2300,9 @@ else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) { else if ( option instanceof ReadOnlyMode ) { loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY ); } + else if ( option instanceof MultiFindOption multiFindOption ) { + throw new IllegalArgumentException( "Option '" + multiFindOption + "' can only be used in 'findMultiple()'" ); + } } if ( lockOptions.getLockMode().isPessimistic() ) { if ( lockOptions.getTimeOut() == WAIT_FOREVER_MILLI ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dynamicmap/FindOperationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dynamicmap/FindOperationTests.java index 4b7e77ec858e..d2b6069a8da8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dynamicmap/FindOperationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dynamicmap/FindOperationTests.java @@ -4,7 +4,10 @@ */ package org.hibernate.orm.test.dynamicmap; +import org.hibernate.IncludeRemovals; +import org.hibernate.OrderedReturn; import org.hibernate.ReadOnlyMode; +import org.hibernate.SessionChecking; import org.hibernate.graph.RootGraph; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; @@ -17,6 +20,7 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Steve Ebersole @@ -58,6 +62,15 @@ void testFindWithOptions(SessionFactoryScope factoryScope) { } ); } + @Test + void testFindWithIllegalOptions(SessionFactoryScope factoryScope) { + factoryScope.inTransaction( (session) -> { + assertThrows( IllegalArgumentException.class, () ->session.find( "artist", 1, SessionChecking.ENABLED ) ); + assertThrows( IllegalArgumentException.class, () ->session.find( "artist", 1, OrderedReturn.ORDERED ) ); + assertThrows( IllegalArgumentException.class, () ->session.find( "artist", 1, IncludeRemovals.INCLUDE ) ); + } ); + } + @Test void testFindWithGraph(SessionFactoryScope factoryScope) { factoryScope.inTransaction( (session) -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadTest.java index d4174d6a13bf..fa143411e7b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadTest.java @@ -4,11 +4,15 @@ */ package org.hibernate.orm.test.loading.multiLoad; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.hibernate.CacheMode; import org.hibernate.Hibernate; +import org.hibernate.IncludeRemovals; +import org.hibernate.OrderedReturn; +import org.hibernate.SessionChecking; import org.hibernate.annotations.BatchSize; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cfg.AvailableSettings; @@ -37,13 +41,13 @@ import jakarta.persistence.Table; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Steve Ebersole @@ -86,7 +90,7 @@ public void testBasicMultiLoad(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); scope.inTransaction( session -> { - statementInspector.getSqlQueries().clear(); + statementInspector.clear(); List list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 5 ) ); assertEquals( 5, list.size() ); @@ -214,10 +218,18 @@ public void testDuplicatedRequestedIdswithDisableOrderedReturn(SessionFactorySco scope.inTransaction( session -> { // un-ordered multiLoad - List list = session.byMultipleIds( SimpleEntity.class ) - .enableOrderedReturn( false ) - .multiLoad( 1, 2, 3, 2, 2 ); - assertEquals( 3, list.size() ); + { + final List list = session.byMultipleIds( SimpleEntity.class ) + .enableOrderedReturn( false ) + .multiLoad( 1, 2, 3, 2, 2 ); + assertEquals( 3, list.size() ); + } + + { + final List list = session.findMultiple( SimpleEntity.class, List.of( 1, 2, 3, 2, 2 ), OrderedReturn.UNORDERED ); + assertEquals( 3, list.size() ); + + } } ); } @@ -228,13 +240,20 @@ public void testNonExistentIdRequest(SessionFactoryScope scope) { scope.inTransaction( session -> { // ordered multiLoad - List list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 ); - assertEquals( 3, list.size() ); - assertNull( list.get( 1 ) ); + { + List list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 ); + assertEquals( 3, list.size() ); + assertNull( list.get( 1 ) ); + + // un-ordered multiLoad + list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 699, 2 ); + assertEquals( 2, list.size() ); + } - // un-ordered multiLoad - list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 699, 2 ); - assertEquals( 2, list.size() ); + { + final List list = session.findMultiple( SimpleEntity.class, List.of(1, 699, 2), OrderedReturn.UNORDERED ); + assertEquals( 2, list.size() ); + } } ); } @@ -257,14 +276,26 @@ public void testBasicMultiLoadWithManagedAndNoChecking(SessionFactoryScope scope public void testBasicMultiLoadWithManagedAndChecking(SessionFactoryScope scope) { scope.inTransaction( session -> { - SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 ); - List list = session.byMultipleIds( SimpleEntity.class ) - .enableSessionCheck( true ) - .multiLoad( ids( 56 ) ); - assertEquals( 56, list.size() ); - // this check is HIGHLY specific to implementation in the batch loader - // which puts existing managed entities first... - assertSame( first, list.get( 0 ) ); + SimpleEntity first; + { + final List list; + first = session.byId( SimpleEntity.class ).load( 1 ); + list = session.byMultipleIds( SimpleEntity.class ) + .enableSessionCheck( true ) + .multiLoad( ids( 56 ) ); + assertEquals( 56, list.size() ); + // this check is HIGHLY specific to implementation in the batch loader + // which puts existing managed entities first... + assertSame( first, list.get( 0 ) ); + } + + { + final List list = session.findMultiple( SimpleEntity.class, idList(56), SessionChecking.ENABLED ); + assertEquals( 56, list.size() ); + // this check is HIGHLY specific to implementation in the batch loader + // which puts existing managed entities first... + assertSame( first, list.get( 0 ) ); + } } ); } @@ -288,13 +319,26 @@ public void testBasicMultiLoadWithManagedAndCheckingProxied(SessionFactoryScope scope.inTransaction( session -> { SimpleEntity first = session.byId( SimpleEntity.class ).getReference( 1 ); - List list = session.byMultipleIds( SimpleEntity.class ) - .enableSessionCheck( true ) - .multiLoad( ids( 56 ) ); - assertEquals( 56, list.size() ); - // this check is HIGHLY specific to implementation in the batch loader - // which puts existing managed entities first... - assertSame( first, list.get( 0 ) ); + { + final List list = session.byMultipleIds( SimpleEntity.class ) + .enableSessionCheck( true ) + .multiLoad( ids( 56 ) ); + assertEquals( 56, list.size() ); + // this check is HIGHLY specific to implementation in the batch loader + // which puts existing managed entities first... + assertSame( first, list.get( 0 ) ); + } + + session.evict( first ); + first = session.byId( SimpleEntity.class ).getReference( 1 ); + + { + final List list = session.findMultiple( SimpleEntity.class, idList(56), SessionChecking.ENABLED ); + assertEquals( 56, list.size() ); + // this check is HIGHLY specific to implementation in the batch loader + // which puts existing managed entities first... + assertSame( first, list.get( 0 ) ); + } } ); } @@ -330,31 +374,59 @@ public void testMultiLoadFrom2ndLevelCache(SessionFactoryScope scope) { // Validate that the entity is still in the Level 2 cache assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) ); - statementInspector.getSqlQueries().clear(); - - // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); - - for(SimpleEntity entity: entities) { - assertTrue( session.contains( entity ) ); + statementInspector.clear(); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + + for ( SimpleEntity entity : entities ) { + assertTrue( session.contains( entity ) ); + } + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' - ); - - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); - } - else { - assertThat( paramCount, is( 2 ) ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED + ); + assertEquals( 3, entities.size() ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + + for ( SimpleEntity entity : entities ) { + assertTrue( session.contains( entity ) ); + } + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); @@ -392,31 +464,60 @@ public void testUnorderedMultiLoadFrom2ndLevelCache(SessionFactoryScope scope) { // Validate that the entity is still in the Level 2 cache assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) ); - statementInspector.getSqlQueries().clear(); - - // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .enableOrderedReturn( false ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + statementInspector.clear(); - for ( SimpleEntity entity : entities ) { - assertTrue( session.contains( entity ) ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .enableOrderedReturn( false ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + + for ( SimpleEntity entity : entities ) { + assertTrue( session.contains( entity ) ); + } + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' - ); - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); - } - else { - assertThat( paramCount, is( 2 ) ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED, + OrderedReturn.UNORDERED + ); + assertEquals( 3, entities.size() ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + + for ( SimpleEntity entity : entities ) { + assertTrue( session.contains( entity ) ); + } + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); @@ -434,27 +535,54 @@ public void testOrderedMultiLoadFrom2ndLevelCachePendingDelete(SessionFactorySco statementInspector.clear(); - // Multi-load 3 items and ensure that it pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .enableOrderedReturn( true ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - - assertNull( entities.get( 1 ) ); - - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' - ); - - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); + { + // Multi-load 3 items and ensure that it pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .enableOrderedReturn( true ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + + assertNull( entities.get( 1 ) ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } - else { - assertThat( paramCount, is( 2 ) ); + + { + // Multi-load 3 items and ensure that it pulls 2 from the database & 1 from the cache. + final List entities = session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED, + OrderedReturn.ORDERED + ); + assertEquals( 3, entities.size() ); + + assertNull( entities.get( 1 ) ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); @@ -472,33 +600,66 @@ public void testOrderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved(Sess statementInspector.clear(); - // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .enableOrderedReturn( true ) - .enableReturnOfDeletedEntities( true ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - - SimpleEntity deletedEntity = entities.get( 1 ); - assertNotNull( deletedEntity ); - - final EntityEntry entry = session.getPersistenceContext() - .getEntry( deletedEntity ); - assertTrue( entry.getStatus().isDeletedOrGone() ); - - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' - ); - - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .enableOrderedReturn( true ) + .enableReturnOfDeletedEntities( true ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + + SimpleEntity deletedEntity = entities.get( 1 ); + assertNotNull( deletedEntity ); + + EntityEntry entry = session.getPersistenceContext() + .getEntry( deletedEntity ); + assertTrue( entry.getStatus().isDeletedOrGone() ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } - else { - assertThat( paramCount, is( 2 ) ); + + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED, + OrderedReturn.ORDERED, + IncludeRemovals.INCLUDE + ); + assertEquals( 3, entities.size() ); + + SimpleEntity deletedEntity = entities.get( 1 ); + assertNotNull( deletedEntity ); + + EntityEntry entry = session.getPersistenceContext() + .getEntry( deletedEntity ); + assertTrue( entry.getStatus().isDeletedOrGone() ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); } @@ -515,27 +676,54 @@ public void testUnorderedMultiLoadFrom2ndLevelCachePendingDelete(SessionFactoryS statementInspector.clear(); - // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .enableOrderedReturn( false ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - - assertTrue( entities.stream().anyMatch( Objects::isNull ) ); - - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' - ); - - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .enableOrderedReturn( false ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + + assertTrue( entities.stream().anyMatch( Objects::isNull ) ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } - else { - assertThat( paramCount, is( 2 ) ); + + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED, + OrderedReturn.UNORDERED + ); + assertEquals( 3, entities.size() ); + + assertTrue( entities.stream().anyMatch( Objects::isNull ) ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); } @@ -552,33 +740,66 @@ public void testUnorderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved(Se statementInspector.clear(); - // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. - List entities = session.byMultipleIds( SimpleEntity.class ) - .with( CacheMode.NORMAL ) - .enableSessionCheck( true ) - .enableOrderedReturn( false ) - .enableReturnOfDeletedEntities( true ) - .multiLoad( ids( 3 ) ); - assertEquals( 3, entities.size() ); - - SimpleEntity deletedEntity = entities.stream().filter( simpleEntity -> simpleEntity.getId() - .equals( 2 ) ).findAny().orElse( null ); - assertNotNull( deletedEntity ); - - final EntityEntry entry = session.getPersistenceContext().getEntry( deletedEntity ); - assertTrue( entry.getStatus().isDeletedOrGone() ); + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities = session.byMultipleIds( SimpleEntity.class ) + .with( CacheMode.NORMAL ) + .enableSessionCheck( true ) + .enableOrderedReturn( false ) + .enableReturnOfDeletedEntities( true ) + .multiLoad( ids( 3 ) ); + assertEquals( 3, entities.size() ); + + SimpleEntity deletedEntity = entities.stream().filter( simpleEntity -> simpleEntity.getId() + .equals( 2 ) ).findAny().orElse( null ); + assertNotNull( deletedEntity ); + + EntityEntry entry = session.getPersistenceContext().getEntry( deletedEntity ); + assertTrue( entry.getStatus().isDeletedOrGone() ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } + } - final int paramCount = StringHelper.countUnquoted( - statementInspector.getSqlQueries().get( 0 ), - '?' + { + // Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache. + final List entities =session.findMultiple( SimpleEntity.class, idList( 3 ), + CacheMode.NORMAL, + SessionChecking.ENABLED, + OrderedReturn.UNORDERED, + IncludeRemovals.INCLUDE ); - - final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); - if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { - assertThat( paramCount, is( 1 ) ); - } - else { - assertThat( paramCount, is( 2 ) ); + assertEquals( 3, entities.size() ); + + SimpleEntity deletedEntity = entities.stream().filter( simpleEntity -> simpleEntity.getId() + .equals( 2 ) ).findAny().orElse( null ); + assertNotNull( deletedEntity ); + + EntityEntry entry = session.getPersistenceContext().getEntry( deletedEntity ); + assertTrue( entry.getStatus().isDeletedOrGone() ); + + final int paramCount = StringHelper.countUnquoted( + statementInspector.getSqlQueries().get( 0 ), + '?' + ); + + final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect(); + if ( MultiKeyLoadHelper.supportsSqlArrayType( dialect ) ) { + assertThat( paramCount, is( 1 ) ); + } + else { + assertThat( paramCount, is( 2 ) ); + } } } ); } @@ -615,15 +836,24 @@ public void testMultiLoadClearsBatchFetchQueue(SessionFactoryScope scope) { .containsEntityKey( entityKey ) ); // now bulk load, which should clean up the BatchFetchQueue entry - List list = session.byMultipleIds( SimpleEntity.class ) - .enableSessionCheck( true ) - .multiLoad( ids( 56 ) ); - - assertEquals( 56, list.size() ); - assertFalse( session.getPersistenceContext() - .getBatchFetchQueue() - .containsEntityKey( entityKey ) ); + { + final List list = session.byMultipleIds( SimpleEntity.class ) + .enableSessionCheck( true ) + .multiLoad( ids( 56 ) ); + assertEquals( 56, list.size() ); + assertFalse( session.getPersistenceContext() + .getBatchFetchQueue() + .containsEntityKey( entityKey ) ); + } + { + final List list = session.findMultiple( SimpleEntity.class, idList( 56 ), + SessionChecking.ENABLED ); + assertEquals( 56, list.size() ); + assertFalse( session.getPersistenceContext() + .getBatchFetchQueue() + .containsEntityKey( entityKey ) ); + } } ); } @@ -636,6 +866,14 @@ private Integer[] ids(int count) { return ids; } + private List idList(int count) { + List ids = new ArrayList<>(count); + for ( int i = 1; i <= count; i++ ) { + ids.add(i); + } + return ids; + } + @Entity( name = "SimpleEntity" ) @Table( name = "SimpleEntity" ) @Cacheable()