HHH-14312 - entity graph is ignored for 'padded' and 'dynamic' batch style entity loader
This commit is contained in:
parent
c874618d30
commit
13fb23d44e
|
@ -329,4 +329,20 @@ public class LockOptions implements Serializable {
|
||||||
destination.setFollowOnLocking( source.getFollowOnLocking() );
|
destination.setFollowOnLocking( source.getFollowOnLocking() );
|
||||||
return destination;
|
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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
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.loader.entity.plan.DynamicBatchingEntityLoaderBuilder;
|
||||||
|
import org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder;
|
||||||
|
import org.hibernate.loader.entity.plan.PaddedBatchingEntityLoaderBuilder;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +26,7 @@ import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
*/
|
*/
|
||||||
public abstract class BatchingEntityLoaderBuilder {
|
public abstract class BatchingEntityLoaderBuilder {
|
||||||
public static BatchingEntityLoaderBuilder getBuilder(SessionFactoryImplementor factory) {
|
public static BatchingEntityLoaderBuilder getBuilder(SessionFactoryImplementor factory) {
|
||||||
switch ( factory.getSettings().getBatchFetchStyle() ) {
|
switch ( factory.getSessionFactoryOptions().getBatchFetchStyle() ) {
|
||||||
case PADDED: {
|
case PADDED: {
|
||||||
return PaddedBatchingEntityLoaderBuilder.INSTANCE;
|
return PaddedBatchingEntityLoaderBuilder.INSTANCE;
|
||||||
}
|
}
|
||||||
|
@ -31,8 +34,7 @@ public abstract class BatchingEntityLoaderBuilder {
|
||||||
return DynamicBatchingEntityLoaderBuilder.INSTANCE;
|
return DynamicBatchingEntityLoaderBuilder.INSTANCE;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder.INSTANCE;
|
return LegacyBatchingEntityLoaderBuilder.INSTANCE;
|
||||||
// return LegacyBatchingEntityLoaderBuilder.INSTANCE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,7 +14,6 @@ import javax.persistence.Table;
|
||||||
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
|
||||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
import org.hibernate.loader.BatchFetchStyle;
|
import org.hibernate.loader.BatchFetchStyle;
|
||||||
|
|
||||||
|
@ -22,6 +21,12 @@ import org.hibernate.testing.TestForIssue;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
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.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@ -31,6 +36,8 @@ import static org.junit.Assert.assertTrue;
|
||||||
*/
|
*/
|
||||||
@TestForIssue( jiraKey = "HHH-14312" )
|
@TestForIssue( jiraKey = "HHH-14312" )
|
||||||
public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctionalTestCase {
|
public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
private static final int BATCH_SIZE = 5;
|
||||||
|
private static final int NUM_OF_LOCATIONS = (BATCH_SIZE * 2) + 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class<?>[] getAnnotatedClasses() {
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
@ -42,56 +49,77 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addConfigOptions(Map options) {
|
protected void addConfigOptions(Map options) {
|
||||||
options.put( AvailableSettings.BATCH_FETCH_STYLE, BatchFetchStyle.PADDED );
|
options.put( AvailableSettings.BATCH_FETCH_STYLE, PADDED );
|
||||||
options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, "50");
|
options.put( AvailableSettings.DEFAULT_BATCH_FETCH_SIZE, BATCH_SIZE );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
doInJPA(
|
||||||
Fruit fruit = new Fruit();
|
this::entityManagerFactory,
|
||||||
fruit.id = 1;
|
entityManager -> {
|
||||||
FruitLocation location1 = new FruitLocation();
|
final Fruit fruit = new Fruit( 1, "Goji" );
|
||||||
location1.fruit = fruit;
|
|
||||||
FruitLocation location2 = new FruitLocation();
|
for ( int i = 1; i <= NUM_OF_LOCATIONS; i++ ) {
|
||||||
location2.fruit = fruit;
|
fruit.locations.add(
|
||||||
fruit.locations.add( location1 );
|
new FruitLocation( i, "Goji location #" + i, fruit )
|
||||||
fruit.locations.add( location2 );
|
);
|
||||||
entityManager.persist( fruit );
|
}
|
||||||
entityManager.persist( location1 );
|
|
||||||
entityManager.persist( location2 );
|
entityManager.persist( fruit );
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEntityGraphSemantic() {
|
public void testEntityGraphSemantic() {
|
||||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
doInJPA(
|
||||||
final RootGraphImplementor<Fruit> graph = (RootGraphImplementor<Fruit>) GraphParser.parse( Fruit.class, "locations", entityManager );
|
this::entityManagerFactory,
|
||||||
final Map<String, Object> hints = Collections.singletonMap( GraphSemantic.FETCH.getJpaHintName(), graph );
|
entityManager -> {
|
||||||
final Fruit fruit = entityManager.find( Fruit.class, 1, hints );
|
final Map<String, Object> hints = Collections.singletonMap(
|
||||||
assertTrue( Hibernate.isInitialized( fruit.locations ) );
|
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")
|
@Entity(name = "Fruit")
|
||||||
@Table(name = "Fruit")
|
@Table(name = "Fruit")
|
||||||
static class Fruit {
|
static class Fruit {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
Integer id;
|
Integer id;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "fruit")
|
@OneToMany( mappedBy = "fruit", cascade = { PERSIST, MERGE, REMOVE } )
|
||||||
Set<FruitLocation> locations = new HashSet<>();
|
Set<FruitLocation> locations = new HashSet<>();
|
||||||
|
|
||||||
|
public Fruit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fruit(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(name = "FruitLocation")
|
@Entity(name = "FruitLocation")
|
||||||
@Table(name = "FruitLocation")
|
@Table(name = "FruitLocation")
|
||||||
static class FruitLocation {
|
static class FruitLocation {
|
||||||
|
|
||||||
@Id @GeneratedValue
|
@Id
|
||||||
Integer id;
|
Integer id;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
|
@ -99,6 +127,15 @@ public class EntityGraphPaddedBatchStyleTest extends BaseEntityManagerFunctional
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
Fruit fruit;
|
Fruit fruit;
|
||||||
|
|
||||||
|
public FruitLocation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public FruitLocation(Integer id, String location, Fruit fruit) {
|
||||||
|
this.id = id;
|
||||||
|
this.location = location;
|
||||||
|
this.fruit = fruit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue