HHH-14312 - entity graph is ignored for 'padded' and 'dynamic' batch style entity loader

This commit is contained in:
Steve Ebersole 2020-11-10 08:36:01 -06:00
parent c874618d30
commit 13fb23d44e
11 changed files with 546 additions and 295 deletions

View File

@ -329,4 +329,20 @@ public class LockOptions implements Serializable {
destination.setFollowOnLocking( source.getFollowOnLocking() );
return destination;
}
public static LockOptions interpret(LockMode lockMode) {
if ( lockMode == null || lockMode == LockMode.NONE ) {
return NONE;
}
if ( lockMode == LockMode.READ ) {
return READ;
}
if ( lockMode.greaterThan( LockMode.UPGRADE_NOWAIT ) ) {
return UPGRADE;
}
return new LockOptions( lockMode );
}
}

View File

@ -10,6 +10,9 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.entity.plan.DynamicBatchingEntityLoaderBuilder;
import org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder;
import org.hibernate.loader.entity.plan.PaddedBatchingEntityLoaderBuilder;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
@ -23,7 +26,7 @@ import org.hibernate.persister.entity.OuterJoinLoadable;
*/
public abstract class BatchingEntityLoaderBuilder {
public static BatchingEntityLoaderBuilder getBuilder(SessionFactoryImplementor factory) {
switch ( factory.getSettings().getBatchFetchStyle() ) {
switch ( factory.getSessionFactoryOptions().getBatchFetchStyle() ) {
case PADDED: {
return PaddedBatchingEntityLoaderBuilder.INSTANCE;
}
@ -31,8 +34,7 @@ public abstract class BatchingEntityLoaderBuilder {
return DynamicBatchingEntityLoaderBuilder.INSTANCE;
}
default: {
return org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder.INSTANCE;
// return LegacyBatchingEntityLoaderBuilder.INSTANCE;
return LegacyBatchingEntityLoaderBuilder.INSTANCE;
}
}
}

View File

@ -1,132 +0,0 @@
/*
* 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.loader.entity;
import java.io.Serializable;
import java.util.List;
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.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* No longer used, see {@link org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder} instead.
*
* @author Steve Ebersole
*/
public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
public static final LegacyBatchingEntityLoaderBuilder INSTANCE = new LegacyBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
public static class LegacyBatchingEntityLoader extends BatchingEntityLoader implements UniqueEntityLoader {
private final int[] batchSizes;
private final Loader[] loaders;
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers);
}
}
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers);
}
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) {
return load( id, optionalObject, session, lockOptions, null );
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) {
final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
for ( int i = 0; i < batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1] != null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
// for now...
final List results = loaders[i].loadEntityBatch(
session,
smallBatch,
persister().getIdentifierType(),
optionalObject,
persister().getEntityName(),
id,
persister(),
lockOptions,
readOnly
);
// 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
}
}
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;
}
}
}

View File

@ -1,135 +0,0 @@
/*
* 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.loader.entity;
import java.io.Serializable;
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.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
public static final PaddedBatchingEntityLoaderBuilder INSTANCE = new PaddedBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new PaddedBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new PaddedBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
public static class PaddedBatchingEntityLoader extends BatchingEntityLoader {
private final int[] batchSizes;
private final Loader[] loaders;
public PaddedBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers);
}
validate( maxBatchSize );
}
private void validate(int max) {
// these are more indicative of internal problems then user error...
if ( batchSizes[0] != max ) {
throw new HibernateException( "Unexpected batch size spread" );
}
if ( batchSizes[batchSizes.length-1] != 1 ) {
throw new HibernateException( "Unexpected batch size spread" );
}
}
public PaddedBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers);
}
validate( maxBatchSize );
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) {
return load( id, optionalObject, session, lockOptions, null );
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) {
final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
final int numberOfIds = ArrayHelper.countNonNull( batch );
if ( numberOfIds <= 1 ) {
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
int indexToUse = batchSizes.length-1;
for ( int i = 0; i < batchSizes.length-1; i++ ) {
if ( batchSizes[i] >= numberOfIds ) {
indexToUse = i;
}
else {
break;
}
}
final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) {
idsToLoad[i] = id;
}
return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions, readOnly );
}
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.loader.entity.plan;
import java.io.Serializable;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.engine.internal.BatchFetchQueueHelper;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class DynamicBatchingEntityLoader extends BatchingEntityLoader {
private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoader.class );
private final int maxBatchSize;
private final EntityLoader.Builder entityLoaderBuilder;
public DynamicBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.maxBatchSize = maxBatchSize;
entityLoaderBuilder = EntityLoader.forEntity( persister )
.withInfluencers( loadQueryInfluencers )
.withLockOptions( lockOptions );
}
@Override
public Object load(
Serializable id,
Object optionalObject,
SharedSessionContractImplementor session,
LockOptions lockOptions) {
return load( id, optionalObject, session, lockOptions, null );
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) {
final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() );
final int numberOfIds = ArrayHelper.countNonNull( batch );
final Serializable[] idsToLoad = new Serializable[ numberOfIds ];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister(), idsToLoad, session.getFactory() ) );
}
final EntityLoader dynamicLoader = entityLoaderBuilder.withBatchSize( idsToLoad.length ).byPrimaryKey();
final QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions );
final List results = dynamicLoader.loadEntityBatch(
session,
idsToLoad,
persister().getIdentifierType(),
optionalObject,
persister().getEntityName(),
id,
persister(),
lockOptions,
readOnly
);
// 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 );
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.loader.entity.plan;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
public class DynamicBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
/**
* Singleton access
*/
public static final DynamicBatchingEntityLoaderBuilder INSTANCE = new DynamicBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return buildBatchingLoader( persister, batchSize, LockOptions.interpret( lockMode ), factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new DynamicBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.loader.entity.plan;
import java.io.Serializable;
import java.util.List;
import org.hibernate.HibernateException;
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.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
public class PaddedBatchingEntityLoader extends BatchingEntityLoader {
private final int[] batchSizes;
private final EntityLoader[] loaders;
public PaddedBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new EntityLoader[ batchSizes.length ];
final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister )
.withInfluencers( loadQueryInfluencers )
.withLockOptions( lockOptions );
// we create a first entity loader to use it as a template for the others
this.loaders[0] = entityLoaderBuilder.withBatchSize( batchSizes[0] ).byPrimaryKey();
for ( int i = 1; i < batchSizes.length; i++ ) {
this.loaders[i] = entityLoaderBuilder.withEntityLoaderTemplate( this.loaders[0] ).withBatchSize( batchSizes[i] ).byPrimaryKey();
}
validate( maxBatchSize );
}
private void validate(int max) {
// these are more indicative of internal problems then user error...
if ( batchSizes[0] != max ) {
throw new HibernateException( "Unexpected batch size spread" );
}
if ( batchSizes[batchSizes.length-1] != 1 ) {
throw new HibernateException( "Unexpected batch size spread" );
}
}
@Override
public Object load(
Serializable id,
Object optionalObject,
SharedSessionContractImplementor session,
LockOptions lockOptions) {
return load( id, optionalObject, session, lockOptions, null );
}
@Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) {
final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
final int numberOfIds = ArrayHelper.countNonNull( batch );
// Uses the first batch-size bigger than the number of actual ids in the batch
int indexToUse = batchSizes.length-1;
for ( int i = 0; i < batchSizes.length-1; i++ ) {
if ( batchSizes[i] >= numberOfIds ) {
indexToUse = i;
}
else {
break;
}
}
final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) {
idsToLoad[i] = id;
}
final List results = loaders[indexToUse].loadEntityBatch(
session,
idsToLoad,
persister().getIdentifierType(),
optionalObject,
persister().getEntityName(),
id,
persister(),
lockOptions,
readOnly
);
// 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 );
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.loader.entity.plan;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
public class PaddedBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
/**
* Singleton access
*/
public static final PaddedBatchingEntityLoaderBuilder INSTANCE = new PaddedBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return buildBatchingLoader( persister, batchSize, LockOptions.interpret( lockMode ), factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new PaddedBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
}

View File

@ -0,0 +1,12 @@
/*
* 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>.
*/
/**
* Support for entity loaders built on top of the {@link org.hibernate.loader.plan}
* API to apply entity-graphs
*/
package org.hibernate.loader.entity.plan;

View File

@ -0,0 +1,143 @@
package org.hibernate.graph;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
import org.hamcrest.CoreMatchers;
import static javax.persistence.CascadeType.MERGE;
import static javax.persistence.CascadeType.PERSIST;
import static javax.persistence.CascadeType.REMOVE;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
import static org.junit.Assert.assertTrue;
/**
* @author David Hoffer
* @author Nathan Xu
*/
@TestForIssue( jiraKey = "HHH-14312" )
public class EntityGraphDynamicBatchStyleTest extends BaseEntityManagerFunctionalTestCase {
private static final int BATCH_SIZE = 5;
private static final int NUM_OF_LOCATIONS = (BATCH_SIZE * 2) + 1;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Fruit.class,
FruitLocation.class
};
}
@Override
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.DYNAMIC );
options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, BATCH_SIZE);
}
@Before
public void setUp() {
inTransaction(
entityManagerFactory(),
entityManager -> {
final Fruit fruit = new Fruit( 1, "Goji" );
for ( int i = 1; i <= NUM_OF_LOCATIONS; i++ ) {
fruit.locations.add(
new FruitLocation( i, "Goji location #" + i, fruit )
);
}
entityManager.persist( fruit );
}
);
}
@Test
public void testEntityGraphSemantic() {
inTransaction(
entityManagerFactory(),
entityManager -> {
final Map<String, Object> hints = Collections.singletonMap(
GraphSemantic.FETCH.getJpaHintName(),
GraphParser.parse( Fruit.class, "locations", entityManager )
);
final Fruit fruit = entityManager.find( Fruit.class, 1, hints );
assertTrue( Hibernate.isInitialized( fruit.locations ) );
assertThat( fruit.locations.size(), is( NUM_OF_LOCATIONS ) );
fruit.locations.forEach(
fruitLocation -> {
assertTrue( Hibernate.isInitialized( fruitLocation ) );
}
);
}
);
}
@Entity(name = "Fruit")
@Table(name = "Fruit")
static class Fruit {
@Id
Integer id;
@Column
String name;
@OneToMany( mappedBy = "fruit", cascade = { PERSIST, MERGE, REMOVE } )
Set<FruitLocation> locations = new HashSet<>();
public Fruit() {
}
public Fruit(Integer id, String name) {
this.id = id;
this.name = name;
}
}
@Entity(name = "FruitLocation")
@Table(name = "FruitLocation")
static class FruitLocation {
@Id
Integer id;
@Column
String location;
@ManyToOne
Fruit fruit;
public FruitLocation() {
}
public FruitLocation(Integer id, String location, Fruit fruit) {
this.id = id;
this.location = location;
this.fruit = fruit;
}
}
}

View File

@ -14,7 +14,6 @@ import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.loader.BatchFetchStyle;
@ -22,6 +21,12 @@ import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
import static javax.persistence.CascadeType.MERGE;
import static javax.persistence.CascadeType.PERSIST;
import static javax.persistence.CascadeType.REMOVE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.loader.BatchFetchStyle.PADDED;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertTrue;
@ -31,6 +36,8 @@ import static org.junit.Assert.assertTrue;
*/
@TestForIssue( jiraKey = "HHH-14312" )
public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctionalTestCase {
private static final int BATCH_SIZE = 5;
private static final int NUM_OF_LOCATIONS = (BATCH_SIZE * 2) + 1;
@Override
protected Class<?>[] getAnnotatedClasses() {
@ -42,56 +49,77 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional
@Override
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED );
options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "50");
options.put( AvailableSettings.BATCH_FETCH_STYLE, PADDED );
options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, BATCH_SIZE );
}
@Before
public void setUp() {
doInJPA( this::entityManagerFactory, entityManager -> {
Fruit fruit = new Fruit();
fruit.id = 1;
FruitLocation location1 = new FruitLocation();
location1.fruit = fruit;
FruitLocation location2 = new FruitLocation();
location2.fruit = fruit;
fruit.locations.add( location1 );
fruit.locations.add( location2 );
entityManager.persist( fruit );
entityManager.persist( location1 );
entityManager.persist( location2 );
} );
doInJPA(
this::entityManagerFactory,
entityManager -> {
final Fruit fruit = new Fruit( 1, "Goji" );
for ( int i = 1; i <= NUM_OF_LOCATIONS; i++ ) {
fruit.locations.add(
new FruitLocation( i, "Goji location #" + i, fruit )
);
}
entityManager.persist( fruit );
}
);
}
@Test
public void testEntityGraphSemantic() {
doInJPA( this::entityManagerFactory, entityManager -> {
final RootGraphImplementor<Fruit> graph = (RootGraphImplementor<Fruit>) GraphParser.parse( Fruit.class, "locations", entityManager );
final Map<String, Object> hints = Collections.singletonMap( GraphSemantic.FETCH.getJpaHintName(), graph );
final Fruit fruit = entityManager.find( Fruit.class, 1, hints );
assertTrue( Hibernate.isInitialized( fruit.locations ) );
} );
doInJPA(
this::entityManagerFactory,
entityManager -> {
final Map<String, Object> hints = Collections.singletonMap(
GraphSemantic.FETCH.getJpaHintName(),
GraphParser.parse( Fruit.class, "locations", entityManager )
);
final Fruit fruit = entityManager.find( Fruit.class, 1, hints );
assertTrue( Hibernate.isInitialized( fruit.locations ) );
assertThat( fruit.locations.size(), is( NUM_OF_LOCATIONS ) );
fruit.locations.forEach(
fruitLocation -> {
assertTrue( Hibernate.isInitialized( fruitLocation ) );
}
);
}
);
}
@Entity(name = "Fruit")
@Table(name = "Fruit")
static class Fruit {
@Id
Integer id;
@Column
String name;
@OneToMany(mappedBy = "fruit")
@OneToMany( mappedBy = "fruit", cascade = { PERSIST, MERGE, REMOVE } )
Set<FruitLocation> locations = new HashSet<>();
public Fruit() {
}
public Fruit(Integer id, String name) {
this.id = id;
this.name = name;
}
}
@Entity(name = "FruitLocation")
@Table(name = "FruitLocation")
static class FruitLocation {
@Id @GeneratedValue
@Id
Integer id;
@Column
@ -99,6 +127,15 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional
@ManyToOne
Fruit fruit;
public FruitLocation() {
}
public FruitLocation(Integer id, String location, Fruit fruit) {
this.id = id;
this.location = location;
this.fruit = fruit;
}
}
}