From 47b029864fc82cbf2c7dc486581453719b09b7de Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 22 Sep 2017 18:10:19 -0700 Subject: [PATCH 1/3] HHH-11970 : @NotFound(IGNORE) and @BatchSize (cherry picked from commit 19087d9f15ca84b98cc32821fc41936ff8419235) --- .../internal/BatchFetchQueueHelper.java | 86 +++++ .../loader/entity/BatchingEntityLoader.java | 10 + .../DynamicBatchingEntityLoaderBuilder.java | 15 +- .../LegacyBatchingEntityLoaderBuilder.java | 18 +- .../PaddedBatchingEntityLoaderBuilder.java | 9 +- .../LegacyBatchingEntityLoaderBuilder.java | 20 +- ...chFetchNotFoundIgnoreDefaultStyleTest.java | 339 ++++++++++++++++++ ...chFetchNotFoundIgnoreDynamicStyleTest.java | 23 ++ ...tchFetchNotFoundIgnorePaddedStyleTest.java | 24 ++ 9 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java new file mode 100644 index 000000000000..c54387a09dad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -0,0 +1,86 @@ +/* + * 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 . + */ +package org.hibernate.engine.internal; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.entity.EntityPersister; + +import org.jboss.logging.Logger; + +/** + * @author Gail Badner + */ +public class BatchFetchQueueHelper { + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, + BatchFetchQueueHelper.class.getName() + ); + + private BatchFetchQueueHelper(){ + } + + /** + * Finds the IDs for entities that were not found when the batch was loaded, and removes + * the corresponding entity keys from the {@link BatchFetchQueue}. + * + * @param ids - the IDs for the entities that were batch loaded + * @param results - the results from loading the batch + * @param persister - the entity persister for the entities in batch + * @param session - the session + */ + public static void removeNotFoundBatchLoadableEntityKeys( + Serializable[] ids, + List results, + EntityPersister persister, + SharedSessionContractImplementor session) { + if ( !persister.isBatchLoadable() ) { + return; + } + if ( ids.length == results.size() ) { + return; + } + LOG.debug( "Not all entities were loaded." ); + Set idSet = new HashSet<>( Arrays.asList( ids ) ); + for ( Object result : results ) { + final Serializable id = session.getPersistenceContext().getEntry( result ).getId(); + idSet.remove( id ); + } + assert idSet.size() == ids.length - results.size(); + if ( LOG.isDebugEnabled() ) { + LOG.debug( "Entities of type [" + persister.getEntityName() + "] not found; IDs: " + idSet ); + } + for ( Serializable id : idSet ) { + removeBatchLoadableEntityKey( id, persister, session ); + } + } + + /** + * Remove the entity key with the specified {@code id} and {@code persister} from + * the batch loadable entities {@link BatchFetchQueue}. + * + * @param id - the ID for the entity to be removed + * @param persister - the entity persister + * @param session - the session + */ + public static void removeBatchLoadableEntityKey( + Serializable id, + EntityPersister persister, + SharedSessionContractImplementor session) { + final EntityKey entityKey = session.generateEntityKey( id, persister ); + final BatchFetchQueue batchFetchQueue = session.getPersistenceContext().getBatchFetchQueue(); + batchFetchQueue.removeBatchLoadableEntityKey( entityKey ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java index ee2232c18f58..1af95359f32d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java @@ -12,6 +12,7 @@ import java.util.List; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.loader.Loader; @@ -97,6 +98,15 @@ protected Object doBatchLoad( try { final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false ); log.debug( "Done entity batch load" ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + ids, + results, + persister(), + session + ); return getObjectFromList(results, id, session); } catch ( SQLException sqle ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index de6261e3d1aa..f0f54f9152e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -18,6 +18,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.dialect.pagination.LimitHelper; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -343,7 +344,13 @@ public Object load( final int numberOfIds = ArrayHelper.countNonNull( batch ); if ( numberOfIds <= 1 ) { - return singleKeyLoader.load( id, optionalObject, session ); + final Object result = singleKeyLoader.load( id, optionalObject, session ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } final Serializable[] idsToLoad = new Serializable[numberOfIds]; @@ -355,6 +362,12 @@ public Object load( QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions ); List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad ); + + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( idsToLoad, results, persister(), session ); + return getObjectFromList( results, id, session ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java index 5494c75acfef..a3017fd289e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -100,10 +101,25 @@ public Object load(Serializable id, Object optionalObject, SessionImplementor se persister(), lockOptions ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + smallBatch, + results, + persister(), + session + ); return getObjectFromList(results, id, session); //EARLY EXIT } } - return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session); + final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java index ff0d8a385f8e..e9ffb9e96a84 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -96,7 +97,13 @@ public Object load(Serializable id, Object optionalObject, SessionImplementor se final int numberOfIds = ArrayHelper.countNonNull( batch ); if ( numberOfIds <= 1 ) { - return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session ); + final Object result = ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } // Uses the first batch-size bigger than the number of actual ids in the batch diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java index 2f204e86dc8f..9580f8e0ad6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -106,12 +107,27 @@ public Object load(Serializable id, Object optionalObject, SessionImplementor se persister(), lockOptions ); + // The EntityKey for any entity that is not found will remain in the batch. + // Explicitly remove the EntityKeys for entities that were not found to + // avoid including them in future batches that get executed. + BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( + smallBatch, + results, + persister(), + session + ); + //EARLY EXIT return getObjectFromList( results, id, session ); } } - return ( loaders[batchSizes.length-1] ).load( id, optionalObject, session, lockOptions ); + final Object result = ( loaders[batchSizes.length-1] ).load( id, optionalObject, session, lockOptions ); + if ( result == null ) { + // There was no entity with the specified ID. Make sure the EntityKey does not remain + // in the batch to avoid including it in future batches that get executed. + BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session ); + } + return result; } } - } diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java new file mode 100644 index 000000000000..01871c44c27c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java @@ -0,0 +1,339 @@ +/* + * 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 . + */ +package org.hibernate.test.batchfetch; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.hibernate.Session; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.BatchFetchQueue; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Gail Badner + * @author Stephen Fikes + */ +public class BatchFetchNotFoundIgnoreDefaultStyleTest extends BaseCoreFunctionalTestCase { + private static final AStatementInspector statementInspector = new AStatementInspector(); + private static final int NUMBER_OF_EMPLOYEES = 8; + + private List tasks = new ArrayList<>(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Employee.class, Task.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.STATEMENT_INSPECTOR, statementInspector ); + } + + @Before + public void createData() { + tasks.clear(); + tasks = doInHibernate( + this::sessionFactory, session -> { + for (int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++) { + Task task = new Task(); + task.id = i; + tasks.add( task ); + session.persist( task ); + Employee e = new Employee("employee0" + i); + e.task = task; + session.persist(e); + } + return tasks; + } + ); + } + + @After + public void deleteData() { + doInHibernate( + this::sessionFactory, session -> { + session.createQuery( "delete from Task" ).executeUpdate(); + session.createQuery( "delete from Employee" ).executeUpdate(); + } + ); + } + + @Test + public void testSeveralNotFoundFromQuery() { + + doInHibernate( + this::sessionFactory, session -> { + // delete 2nd and 8th Task so that the non-found Task entities will be queried + // in 2 different batches. + session.delete( tasks.get( 1 ) ); + session.delete( tasks.get( 7 ) ); + } + ); + + statementInspector.clear(); + + final List employees = doInHibernate( + this::sessionFactory, session -> { + List results = + session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + return results; + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 4 SQL statements executed + assertEquals( 4, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 0, paramterCounts.get( 0 ).intValue() ); + + // query specifically for Task with ID == 0 will result in 1st batch; + // query should have 5 parameters for [0,1,2,3,4]; + // Task with ID == 1 won't be found; the rest will be found. + assertEquals( 5, paramterCounts.get( 1 ).intValue() ); + + // query specifically for Task with ID == 1 will result in 2nd batch; + // query should have 4 parameters [1,5,6,7]; + // Task with IDs == [1,7] won't be found; the rest will be found. + assertEquals( 4, paramterCounts.get( 2 ).intValue() ); + + // no extra queries required to load entities with IDs [2,3,4] because they + // were already loaded from 1st batch + + // no extra queries required to load entities with IDs [5,6] because they + // were already loaded from 2nd batch + + // query specifically for Task with ID == 7 will result in just querying + // Task with ID == 7 (because the batch is empty). + // query should have 1 parameter [7]; + // Task with ID == 7 won't be found. + assertEquals( 1, paramterCounts.get( 3 ).intValue() ); + + assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); + for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + if ( i == 1 || i == 7 ) { + assertNull( employees.get( i ).task ); + } + else { + assertEquals( tasks.get( i ).id, employees.get( i ).task.id ); + } + } + } + + @Test + public void testMostNotFoundFromQuery() { + + doInHibernate( + this::sessionFactory, session -> { + // delete all but last Task entity + for ( int i = 0; i < 7; i++ ) { + session.delete( tasks.get( i ) ); + } + } + ); + + statementInspector.clear(); + + final List employees = doInHibernate( + this::sessionFactory, session -> { + List results = + session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + return results; + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 8 SQL statements executed + assertEquals( 8, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 0, paramterCounts.get( 0 ).intValue() ); + + // query specifically for Task with ID == 0 will result in 1st batch; + // query should have 5 parameters for [0,1,2,3,4]; + // Task with IDs == [0,1,2,3,4] won't be found + assertEquals( 5, paramterCounts.get( 1 ).intValue() ); + + // query specifically for Task with ID == 1 will result in 2nd batch; + // query should have 4 parameters [1,5,6,7]; + // Task with IDs == [1,5,6] won't be found; Task with ID == 7 will be found. + assertEquals( 4, paramterCounts.get( 2 ).intValue() ); + + // query specifically for Task with ID == 2 will result in just querying + // Task with ID == 2 (because the batch is empty). + // query should have 1 parameter [2]; + // Task with ID == 2 won't be found. + assertEquals( 1, paramterCounts.get( 3 ).intValue() ); + + // query specifically for Task with ID == 3 will result in just querying + // Task with ID == 3 (because the batch is empty). + // query should have 1 parameter [3]; + // Task with ID == 3 won't be found. + assertEquals( 1, paramterCounts.get( 4 ).intValue() ); + + // query specifically for Task with ID == 4 will result in just querying + // Task with ID == 4 (because the batch is empty). + // query should have 1 parameter [4]; + // Task with ID == 4 won't be found. + assertEquals( 1, paramterCounts.get( 5 ).intValue() ); + + // query specifically for Task with ID == 5 will result in just querying + // Task with ID == 5 (because the batch is empty). + // query should have 1 parameter [5]; + // Task with ID == 5 won't be found. + assertEquals( 1, paramterCounts.get( 6 ).intValue() ); + + // query specifically for Task with ID == 6 will result in just querying + // Task with ID == 6 (because the batch is empty). + // query should have 1 parameter [6]; + // Task with ID == 6 won't be found. + assertEquals( 1, paramterCounts.get( 7 ).intValue() ); + + // no extra queries required to load entity with ID == 7 because it + // was already loaded from 2nd batch + + assertEquals( NUMBER_OF_EMPLOYEES, employees.size() ); + + for ( int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++ ) { + if ( i == 7 ) { + assertEquals( tasks.get( i ).id, employees.get( i ).task.id ); + } + else { + assertNull( employees.get( i ).task ); + } + } + } + + @Test + public void testNotFoundFromGet() { + + doInHibernate( + this::sessionFactory, session -> { + // delete task so it is not found later when getting the Employee. + session.delete( tasks.get( 0 ) ); + } + ); + + statementInspector.clear(); + + doInHibernate( + this::sessionFactory, session -> { + Employee employee = session.get( Employee.class, "employee00" ); + checkInBatchFetchQueue( tasks.get( 0 ).id, session, false ); + assertNotNull( employee ); + assertNull( employee.task ); + } + ); + + final List paramterCounts = statementInspector.parameterCounts; + + // there should be 2 SQL statements executed + // 1) query to load Employee entity by ID (associated Tasks is registered for batch loading) + // 2) batch will only contain the ID for the associated Task (which will not be found) + assertEquals( 2, paramterCounts.size() ); + + // query loading Employee entities shouldn't have any parameters + assertEquals( 1, paramterCounts.get( 0 ).intValue() ); + + // Will result in just querying a single Task (because the batch is empty). + // query should have 1 parameter; + // Task won't be found. + assertEquals( 1, paramterCounts.get( 1 ).intValue() ); + } + + private static void checkInBatchFetchQueue(long id, Session session, boolean expected) { + final SessionImplementor sessionImplementor = (SessionImplementor) session; + final EntityPersister persister = + sessionImplementor.getFactory().getMetamodel().entityPersister( Task.class ); + final BatchFetchQueue batchFetchQueue = + sessionImplementor.getPersistenceContext().getBatchFetchQueue(); + assertEquals( expected, batchFetchQueue.containsEntityKey( new EntityKey( id, persister ) ) ); + } + + @Entity(name = "Employee") + public static class Employee { + @Id + private String name; + + @OneToOne(optional = true) + @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + @NotFound(action = NotFoundAction.IGNORE) + private Task task; + + private Employee() { + } + + private Employee(String name) { + this.name = name; + } + } + + @Entity(name = "Task") + @BatchSize(size = 5) + public static class Task { + @Id + private long id; + + public Task() { + } + } + + public static class AStatementInspector implements StatementInspector { + private List parameterCounts = new ArrayList<>(); + + public String inspect(String sql) { + parameterCounts.add( countParameters( sql ) ); + return sql; + } + private void clear() { + parameterCounts.clear(); + } + private int countParameters(String sql) { + int count = 0; + int parameterIndex = sql.indexOf( '?' ); + while ( parameterIndex >= 0 ) { + count++; + parameterIndex = sql.indexOf( '?', parameterIndex + 1 ); + } + return count; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java new file mode 100644 index 000000000000..18aebd94a178 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDynamicStyleTest.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ +package org.hibernate.test.batchfetch; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.loader.BatchFetchStyle; + +/** + * @author Gail Badner + */ +public class BatchFetchNotFoundIgnoreDynamicStyleTest extends BatchFetchNotFoundIgnoreDefaultStyleTest { + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.BATCH_FETCH_STYLE, BatchFetchStyle.DYNAMIC ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java new file mode 100644 index 000000000000..e02a726612c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnorePaddedStyleTest.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ +package org.hibernate.test.batchfetch; + +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.loader.BatchFetchStyle; + +/** + * @author Gail Badner + */ +public class BatchFetchNotFoundIgnorePaddedStyleTest extends BatchFetchNotFoundIgnoreDefaultStyleTest { + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.getProperties().put( Environment.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED ); + } + +} From 5cadbe02a70c3df7378f8eca65c49a054cb46073 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 26 Sep 2017 22:10:48 -0700 Subject: [PATCH 2/3] HHH-11970 : @NotFound(IGNORE) and @BatchSize (cherry picked from commit 86f310e4cb2ab48b4ad9f9d327a172b08b86281f) --- .../org/hibernate/engine/internal/BatchFetchQueueHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java index c54387a09dad..5d04bbaaa909 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -55,8 +55,8 @@ public static void removeNotFoundBatchLoadableEntityKeys( LOG.debug( "Not all entities were loaded." ); Set idSet = new HashSet<>( Arrays.asList( ids ) ); for ( Object result : results ) { - final Serializable id = session.getPersistenceContext().getEntry( result ).getId(); - idSet.remove( id ); + // All results should be in the PersistenceContext + idSet.remove( session.getContextEntityIdentifier( result ) ); } assert idSet.size() == ids.length - results.size(); if ( LOG.isDebugEnabled() ) { From 4db1510c74fad78d4a09fca1d0529002e23f30ac Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 5 Jan 2018 16:45:41 -0800 Subject: [PATCH 3/3] HHH-11970 : Fixes to work in pre-5.2 --- .../internal/BatchFetchQueueHelper.java | 8 +- ...chFetchNotFoundIgnoreDefaultStyleTest.java | 115 ++++++++++-------- 2 files changed, 69 insertions(+), 54 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java index 5d04bbaaa909..4cdfe946866c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/BatchFetchQueueHelper.java @@ -14,7 +14,7 @@ import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; @@ -45,7 +45,7 @@ public static void removeNotFoundBatchLoadableEntityKeys( Serializable[] ids, List results, EntityPersister persister, - SharedSessionContractImplementor session) { + SessionImplementor session) { if ( !persister.isBatchLoadable() ) { return; } @@ -53,7 +53,7 @@ public static void removeNotFoundBatchLoadableEntityKeys( return; } LOG.debug( "Not all entities were loaded." ); - Set idSet = new HashSet<>( Arrays.asList( ids ) ); + Set idSet = new HashSet( Arrays.asList( ids ) ); for ( Object result : results ) { // All results should be in the PersistenceContext idSet.remove( session.getContextEntityIdentifier( result ) ); @@ -78,7 +78,7 @@ public static void removeNotFoundBatchLoadableEntityKeys( public static void removeBatchLoadableEntityKey( Serializable id, EntityPersister persister, - SharedSessionContractImplementor session) { + SessionImplementor session) { final EntityKey entityKey = session.generateEntityKey( id, persister ); final BatchFetchQueue batchFetchQueue = session.getPersistenceContext().getBatchFetchQueue(); batchFetchQueue.removeBatchLoadableEntityKey( entityKey ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java index 01871c44c27c..2f953d2e8b67 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/batchfetch/BatchFetchNotFoundIgnoreDefaultStyleTest.java @@ -32,7 +32,6 @@ import org.junit.Before; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -61,8 +60,10 @@ protected void configure(Configuration configuration) { @Before public void createData() { tasks.clear(); - tasks = doInHibernate( - this::sessionFactory, session -> { + + Session session = openSession(); + session.getTransaction().begin(); + { for (int i = 0 ; i < NUMBER_OF_EMPLOYEES ; i++) { Task task = new Task(); task.id = i; @@ -72,45 +73,51 @@ public void createData() { e.task = task; session.persist(e); } - return tasks; - } - ); + } + session.getTransaction().commit(); + session.close(); } @After public void deleteData() { - doInHibernate( - this::sessionFactory, session -> { + Session session = openSession(); + session.getTransaction().begin(); + { session.createQuery( "delete from Task" ).executeUpdate(); session.createQuery( "delete from Employee" ).executeUpdate(); - } - ); + } + session.getTransaction().commit(); + session.close(); } @Test + @SuppressWarnings("unchecked") public void testSeveralNotFoundFromQuery() { - doInHibernate( - this::sessionFactory, session -> { + Session session = openSession(); + session.getTransaction().begin(); + { // delete 2nd and 8th Task so that the non-found Task entities will be queried // in 2 different batches. session.delete( tasks.get( 1 ) ); session.delete( tasks.get( 7 ) ); - } - ); + } + session.getTransaction().commit(); + session.close(); statementInspector.clear(); - final List employees = doInHibernate( - this::sessionFactory, session -> { - List results = - session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); - for ( int i = 0 ; i < tasks.size() ; i++ ) { - checkInBatchFetchQueue( tasks.get( i ).id, session, false ); - } - return results; - } - ); + session = openSession(); + session.getTransaction().begin(); + + final List employees = + (List) session.createQuery( "from Employee e order by e.id" ).list(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + + session.getTransaction().commit(); + session.close(); final List paramterCounts = statementInspector.parameterCounts; @@ -154,29 +161,33 @@ public void testSeveralNotFoundFromQuery() { } @Test + @SuppressWarnings("unchecked") public void testMostNotFoundFromQuery() { - doInHibernate( - this::sessionFactory, session -> { + Session session = openSession(); + session.getTransaction().begin(); + { // delete all but last Task entity for ( int i = 0; i < 7; i++ ) { session.delete( tasks.get( i ) ); } - } - ); + } + session.getTransaction().commit(); + session.close(); statementInspector.clear(); - final List employees = doInHibernate( - this::sessionFactory, session -> { - List results = - session.createQuery( "from Employee e order by e.id", Employee.class ).getResultList(); - for ( int i = 0 ; i < tasks.size() ; i++ ) { - checkInBatchFetchQueue( tasks.get( i ).id, session, false ); - } - return results; - } - ); + session = openSession(); + session.getTransaction().begin(); + + final List employees = + (List) session.createQuery( "from Employee e order by e.id" ).list(); + for ( int i = 0 ; i < tasks.size() ; i++ ) { + checkInBatchFetchQueue( tasks.get( i ).id, session, false ); + } + + session.getTransaction().commit(); + session.close(); final List paramterCounts = statementInspector.parameterCounts; @@ -242,25 +253,29 @@ public void testMostNotFoundFromQuery() { } @Test - public void testNotFoundFromGet() { + public void testNotFoundFromGet ( ) { - doInHibernate( - this::sessionFactory, session -> { - // delete task so it is not found later when getting the Employee. - session.delete( tasks.get( 0 ) ); - } - ); + Session session = openSession(); + session.getTransaction().begin(); + { + // delete task so it is not found later when getting the Employee. + session.delete( tasks.get( 0 ) ); + } + session.getTransaction().commit(); + session.close(); statementInspector.clear(); - doInHibernate( - this::sessionFactory, session -> { + session = openSession(); + session.getTransaction().begin(); + { Employee employee = session.get( Employee.class, "employee00" ); checkInBatchFetchQueue( tasks.get( 0 ).id, session, false ); assertNotNull( employee ); assertNull( employee.task ); - } - ); + } + session.getTransaction().commit(); + session.close(); final List paramterCounts = statementInspector.parameterCounts; @@ -281,7 +296,7 @@ public void testNotFoundFromGet() { private static void checkInBatchFetchQueue(long id, Session session, boolean expected) { final SessionImplementor sessionImplementor = (SessionImplementor) session; final EntityPersister persister = - sessionImplementor.getFactory().getMetamodel().entityPersister( Task.class ); + sessionImplementor.getFactory().getEntityPersister( Task.class.getName() ); final BatchFetchQueue batchFetchQueue = sessionImplementor.getPersistenceContext().getBatchFetchQueue(); assertEquals( expected, batchFetchQueue.containsEntityKey( new EntityKey( id, persister ) ) );