HHH-11970 : @NotFound(IGNORE) and @BatchSize
This commit is contained in:
parent
87a37b02f4
commit
19087d9f15
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
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<Serializable> 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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
||||||
import org.hibernate.engine.spi.QueryParameters;
|
import org.hibernate.engine.spi.QueryParameters;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.loader.Loader;
|
import org.hibernate.loader.Loader;
|
||||||
|
@ -97,6 +98,15 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader {
|
||||||
try {
|
try {
|
||||||
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );
|
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );
|
||||||
log.debug( "Done entity batch load" );
|
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);
|
return getObjectFromList(results, id, session);
|
||||||
}
|
}
|
||||||
catch ( SQLException sqle ) {
|
catch ( SQLException sqle ) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.util.List;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.dialect.pagination.LimitHelper;
|
import org.hibernate.dialect.pagination.LimitHelper;
|
||||||
|
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
|
@ -344,7 +345,13 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
|
||||||
|
|
||||||
final int numberOfIds = ArrayHelper.countNonNull( batch );
|
final int numberOfIds = ArrayHelper.countNonNull( batch );
|
||||||
if ( numberOfIds <= 1 ) {
|
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];
|
final Serializable[] idsToLoad = new Serializable[numberOfIds];
|
||||||
|
@ -356,6 +363,12 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
|
||||||
|
|
||||||
QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions );
|
QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions );
|
||||||
List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad );
|
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 );
|
return getObjectFromList( results, id, session );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -100,10 +101,25 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild
|
||||||
persister(),
|
persister(),
|
||||||
lockOptions
|
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 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.io.Serializable;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -96,7 +97,13 @@ class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
|
||||||
|
|
||||||
final int numberOfIds = ArrayHelper.countNonNull( batch );
|
final int numberOfIds = ArrayHelper.countNonNull( batch );
|
||||||
if ( numberOfIds <= 1 ) {
|
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
|
// Uses the first batch-size bigger than the number of actual ids in the batch
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -106,12 +107,27 @@ public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoa
|
||||||
persister(),
|
persister(),
|
||||||
lockOptions
|
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
|
//EARLY EXIT
|
||||||
return getObjectFromList( results, id, session );
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
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<Task> 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<Employee> employees = doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
List<Employee> 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<Integer> 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<Employee> employees = doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
List<Employee> 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<Integer> 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<Integer> 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<Integer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue