HHH-10679 HHH-10712 : Fix subselect fetching in load plans

(cherry picked from commit d444be1fdbf3618ce1f52b6ea8b8812fbfd795db)

Conflicts:
	hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java
This commit is contained in:
Gail Badner 2016-04-26 13:34:15 -07:00
parent 545930c39f
commit 62ac35213f
18 changed files with 1250 additions and 104 deletions

View File

@ -44,7 +44,11 @@ public final class StringHelper {
if ( length == 0 ) {
return "";
}
StringBuilder buf = new StringBuilder( length * strings[0].length() )
// Allocate space for length * firstStringLength;
// If strings[0] is null, then its length is defined as 4, since that's the
// length of "null".
final int firstStringLength = strings[0] != null ? strings[0].length() : 4;
StringBuilder buf = new StringBuilder( length * firstStringLength )
.append( strings[0] );
for ( int i = 1; i < length; i++ ) {
buf.append( seperator ).append( strings[i] );

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl;
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.type.Type;
@ -63,28 +64,14 @@ public class CollectionLoader extends AbstractLoadPlanBasedCollectionInitializer
}
public CollectionLoader byKey() {
final QueryBuildingParameters buildingParameters = new QueryBuildingParameters() {
@Override
public LoadQueryInfluencers getQueryInfluencers() {
return influencers;
}
@Override
public int getBatchSize() {
return batchSize;
}
@Override
public LockMode getLockMode() {
return LockMode.NONE;
}
@Override
public LockOptions getLockOptions() {
return null;
}
};
return new CollectionLoader( collectionPersister, buildingParameters ) ;
// capture current values in a new instance of QueryBuildingParametersImpl
final QueryBuildingParameters currentBuildingParameters = new QueryBuildingParametersImpl(
influencers,
batchSize,
LockMode.NONE,
null
);
return new CollectionLoader( collectionPersister, currentBuildingParameters ) ;
}
}

View File

@ -12,6 +12,7 @@ import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl;
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.type.Type;
@ -77,32 +78,18 @@ public class EntityLoader extends AbstractLoadPlanBasedEntityLoader {
}
public EntityLoader byUniqueKey(String[] keyColumnNames, Type keyType) {
// capture current values in a new instance of QueryBuildingParametersImpl
return new EntityLoader(
persister.getFactory(),
persister,
keyColumnNames,
keyType,
new QueryBuildingParameters() {
@Override
public LoadQueryInfluencers getQueryInfluencers() {
return influencers;
}
@Override
public int getBatchSize() {
return batchSize;
}
@Override
public LockMode getLockMode() {
return lockMode;
}
@Override
public LockOptions getLockOptions() {
return lockOptions;
}
}
new QueryBuildingParametersImpl(
influencers,
batchSize,
lockMode,
lockOptions
)
);
}
}

View File

@ -39,6 +39,7 @@ import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.FetchSource;
import org.hibernate.loader.plan.spi.Return;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.walking.internal.FetchStrategyHelper;
import org.hibernate.persister.walking.spi.AnyMappingDefinition;
import org.hibernate.persister.walking.spi.AssociationAttributeDefinition;
import org.hibernate.persister.walking.spi.AssociationKey;
@ -833,9 +834,6 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
protected boolean handleAssociationAttribute(AssociationAttributeDefinition attributeDefinition) {
// todo : this seems to not be correct for one-to-one
final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition );
if ( fetchStrategy.getTiming() != FetchTiming.IMMEDIATE ) {
return false;
}
final ExpandingFetchSource currentSource = currentSource();
currentSource.validateFetchPlan( fetchStrategy, attributeDefinition );
@ -845,6 +843,7 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
// for ANY mappings we need to build a Fetch:
// 1) fetch type is SELECT
// 2) (because the fetch cannot be a JOIN...) do not push it to the stack
// regardless of the fetch style, build the fetch
currentSource.buildAnyAttributeFetch(
attributeDefinition,
fetchStrategy
@ -852,11 +851,13 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
return false;
}
else if ( nature == AssociationAttributeDefinition.AssociationNature.ENTITY ) {
// regardless of the fetch style, build the fetch
EntityFetch fetch = currentSource.buildEntityAttributeFetch(
attributeDefinition,
fetchStrategy
);
if ( fetchStrategy.getStyle() == FetchStyle.JOIN ) {
if ( FetchStrategyHelper.isJoinFetched( fetchStrategy ) ) {
// only push to the stack if join fetched
pushToStack( (ExpandingFetchSource) fetch );
return true;
}
@ -866,8 +867,13 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
}
else {
// Collection
CollectionAttributeFetch fetch = currentSource.buildCollectionAttributeFetch( attributeDefinition, fetchStrategy );
if ( fetchStrategy.getStyle() == FetchStyle.JOIN ) {
// regardless of the fetch style, build the fetch
CollectionAttributeFetch fetch = currentSource.buildCollectionAttributeFetch(
attributeDefinition,
fetchStrategy
);
if ( FetchStrategyHelper.isJoinFetched( fetchStrategy ) ) {
// only push to the stack if join fetched
pushToCollectionStack( fetch );
return true;
}

View File

@ -91,6 +91,16 @@ public abstract class AbstractCollectionLoadQueryDetails extends AbstractLoadQue
return (CollectionReturn) getRootReturn();
}
@Override
protected boolean isSubselectLoadingEnabled(FetchStats fetchStats) {
return fetchStats != null && fetchStats.hasSubselectFetches();
}
@Override
protected boolean shouldUseOptionalEntityInstance() {
return false;
}
@Override
protected ReaderCollector getReaderCollector() {
return readerCollector;

View File

@ -173,11 +173,22 @@ public abstract class AbstractLoadQueryDetails implements LoadQueryDetails {
this.sqlStatement = select.toStatementString();
this.resultSetProcessor = new ResultSetProcessorImpl(
loadPlan,
queryProcessor.getAliasResolutionContext(),
getReaderCollector().buildRowReader(),
fetchStats != null && fetchStats.hasSubselectFetches()
shouldUseOptionalEntityInstance(),
isSubselectLoadingEnabled( fetchStats )
);
}
/**
* Is subselect loading enabled?
*
* @param fetchStats the fetch stats; may be null
* @return {@code true} if subselect loading is enabled; {@code false} otherwise.
*/
protected abstract boolean isSubselectLoadingEnabled(FetchStats fetchStats);
protected abstract boolean shouldUseOptionalEntityInstance();
protected abstract ReaderCollector getReaderCollector();
protected abstract QuerySpace getRootQuerySpace();
protected abstract String getRootTableAlias();

View File

@ -152,6 +152,18 @@ public class EntityLoadQueryDetails extends AbstractLoadQueryDetails {
protected void applyRootReturnOrderByFragments(SelectStatementBuilder selectStatementBuilder) {
}
@Override
protected boolean isSubselectLoadingEnabled(FetchStats fetchStats) {
return getQueryBuildingParameters().getBatchSize() > 1 &&
fetchStats != null &&
fetchStats.hasSubselectFetches();
}
@Override
protected boolean shouldUseOptionalEntityInstance() {
return getQueryBuildingParameters().getBatchSize() < 2;
}
@Override
protected ReaderCollector getReaderCollector() {
return readerCollector;

View File

@ -443,10 +443,8 @@ public class LoadQueryJoinAndFetchProcessor {
Fetch fetch,
ReaderCollector readerCollector,
FetchStatsImpl fetchStats) {
if ( ! FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ) ) {
return;
}
// process fetch even if it is not join fetched
if ( EntityFetch.class.isInstance( fetch ) ) {
final EntityFetch entityFetch = (EntityFetch) fetch;
processEntityFetch(
@ -494,6 +492,10 @@ public class LoadQueryJoinAndFetchProcessor {
// }
fetchStats.processingFetch( fetch );
if ( ! FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ) ) {
// not join fetched, so nothing else to do
return;
}
// First write out the SQL SELECT fragments
final Joinable joinable = (Joinable) fetch.getEntityPersister();
@ -540,8 +542,14 @@ public class LoadQueryJoinAndFetchProcessor {
CollectionAttributeFetch fetch,
ReaderCollector readerCollector,
FetchStatsImpl fetchStats) {
fetchStats.processingFetch( fetch );
if ( ! FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ) ) {
// not join fetched, so nothing else to do
return;
}
final CollectionReferenceAliases aliases = aliasResolutionContext.resolveCollectionReferenceAliases(
fetch.getQuerySpaceUid()
);

View File

@ -15,13 +15,17 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext;
import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext;
import org.hibernate.loader.plan.exec.spi.AliasResolutionContext;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReference;
import org.hibernate.loader.plan.spi.Fetch;
@ -34,9 +38,12 @@ import org.hibernate.type.EntityType;
* @author Steve Ebersole
*/
public class ResultSetProcessingContextImpl implements ResultSetProcessingContext {
private static final Logger LOG = CoreLogging.logger( ResultSetProcessingContextImpl.class );
private final ResultSet resultSet;
private final SharedSessionContractImplementor session;
private final LoadPlan loadPlan;
private final AliasResolutionContext aliasResolutionContext;
private final boolean readOnly;
private final boolean shouldUseOptionalEntityInformation;
private final boolean forceFetchLazyAttributes;
@ -47,8 +54,9 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
private List<HydratedEntityRegistration> currentRowHydratedEntityRegistrationList;
private Map<EntityPersister,Set<EntityKey>> subselectLoadableEntityKeyMap;
private Map<EntityReference,Set<EntityKey>> subselectLoadableEntityKeyMap;
private List<HydratedEntityRegistration> hydratedEntityRegistrationList;
private int nRowsRead = 0;
/**
* Builds a ResultSetProcessingContextImpl
@ -61,6 +69,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
final ResultSet resultSet,
final SharedSessionContractImplementor session,
final LoadPlan loadPlan,
final AliasResolutionContext aliasResolutionContext,
final boolean readOnly,
final boolean shouldUseOptionalEntityInformation,
final boolean forceFetchLazyAttributes,
@ -71,6 +80,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
this.resultSet = resultSet;
this.session = session;
this.loadPlan = loadPlan;
this.aliasResolutionContext = aliasResolutionContext;
this.readOnly = readOnly;
this.shouldUseOptionalEntityInformation = shouldUseOptionalEntityInformation;
this.forceFetchLazyAttributes = forceFetchLazyAttributes;
@ -254,6 +264,8 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
* Package-protected
*/
void finishUpRow() {
nRowsRead++;
if ( currentRowHydratedEntityRegistrationList == null ) {
if ( identifierResolutionContextMap != null ) {
identifierResolutionContextMap.clear();
@ -276,11 +288,11 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
}
for ( HydratedEntityRegistration registration : currentRowHydratedEntityRegistrationList ) {
Set<EntityKey> entityKeys = subselectLoadableEntityKeyMap.get(
registration.getEntityReference().getEntityPersister()
registration.getEntityReference()
);
if ( entityKeys == null ) {
entityKeys = new HashSet<>();
subselectLoadableEntityKeyMap.put( registration.getEntityReference().getEntityPersister(), entityKeys );
subselectLoadableEntityKeyMap.put( registration.getEntityReference(), entityKeys );
}
entityKeys.add( registration.getKey() );
}
@ -314,24 +326,27 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex
}
private void createSubselects() {
if ( subselectLoadableEntityKeyMap == null || subselectLoadableEntityKeyMap.size() <= 1 ) {
// if we only returned one entity, query by key is more efficient; so do nothing here
return;
if ( subselectLoadableEntityKeyMap == null || nRowsRead <= 1 ) {
LOG.tracef(
"Skipping create subselects because there are fewer than 2 results, so query by key is more efficient.",
getClass().getName()
);
return; // early return
}
final Map<String, int[]> namedParameterLocMap =
ResultSetProcessorHelper.buildNamedParameterLocMap( queryParameters, namedParameterContext );
final String subselectQueryString = SubselectFetch.createSubselectFetchQueryFragment( queryParameters );
for ( Map.Entry<EntityPersister, Set<EntityKey>> entry : subselectLoadableEntityKeyMap.entrySet() ) {
if ( ! entry.getKey().hasSubselectLoadableCollections() ) {
for ( Map.Entry<EntityReference, Set<EntityKey>> entry : subselectLoadableEntityKeyMap.entrySet() ) {
if ( ! entry.getKey().getEntityPersister().hasSubselectLoadableCollections() ) {
continue;
}
SubselectFetch subselectFetch = new SubselectFetch(
subselectQueryString,
null, // aliases[i],
(Loadable) entry.getKey(),
aliasResolutionContext.resolveSqlTableAliasFromQuerySpaceUid( entry.getKey().getQuerySpaceUid() ),
(Loadable) entry.getKey().getEntityPersister(),
queryParameters,
entry.getValue(),
namedParameterLocMap

View File

@ -21,6 +21,7 @@ import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessor;
import org.hibernate.loader.plan.exec.process.spi.RowReader;
import org.hibernate.loader.plan.exec.process.spi.ScrollableResultSetProcessor;
import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext;
import org.hibernate.loader.plan.exec.spi.AliasResolutionContext;
import org.hibernate.loader.plan.spi.CollectionReturn;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.spi.AfterLoadAction;
@ -37,13 +38,27 @@ public class ResultSetProcessorImpl implements ResultSetProcessor {
private static final Logger LOG = Logger.getLogger( ResultSetProcessorImpl.class );
private final LoadPlan loadPlan;
private final AliasResolutionContext aliasResolutionContext;
private final RowReader rowReader;
private final boolean hadSubselectFetches;
public ResultSetProcessorImpl(LoadPlan loadPlan, RowReader rowReader, boolean hadSubselectFetches) {
private final boolean shouldUseOptionalEntityInstance;
// There are times when the "optional entity information" on QueryParameters should be used and
// times when they should be ignored. Loader uses its isSingleRowLoader method to allow
// subclasses to override that. Collection initializers, batch loaders, e.g. override that
// it to be false. The 'shouldUseOptionalEntityInstance' setting is meant to fill that same role.
public ResultSetProcessorImpl(
LoadPlan loadPlan,
AliasResolutionContext aliasResolutionContext,
RowReader rowReader,
boolean shouldUseOptionalEntityInstance,
boolean hadSubselectFetches) {
this.loadPlan = loadPlan;
this.aliasResolutionContext = aliasResolutionContext;
this.rowReader = rowReader;
this.shouldUseOptionalEntityInstance = shouldUseOptionalEntityInstance;
this.hadSubselectFetches = hadSubselectFetches;
}
@ -80,12 +95,6 @@ public class ResultSetProcessorImpl implements ResultSetProcessor {
maxRows = Integer.MAX_VALUE;
}
// There are times when the "optional entity information" on QueryParameters should be used and
// times when they should be ignored. Loader uses its isSingleRowLoader method to allow
// subclasses to override that. Collection initializers, batch loaders, e.g. override that
// it to be false. The 'shouldUseOptionalEntityInstance' setting is meant to fill that same role.
final boolean shouldUseOptionalEntityInstance = true;
// Handles the "FETCH ALL PROPERTIES" directive in HQL
final boolean forceFetchLazyAttributes = false;
@ -93,6 +102,7 @@ public class ResultSetProcessorImpl implements ResultSetProcessor {
resultSet,
session,
loadPlan,
aliasResolutionContext,
readOnly,
shouldUseOptionalEntityInstance,
forceFetchLazyAttributes,

View File

@ -0,0 +1,53 @@
/*
* 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.plan.exec.query.internal;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
/**
* @author Gail Badner
*/
public class QueryBuildingParametersImpl implements QueryBuildingParameters {
private final LoadQueryInfluencers loadQueryInfluencers;
private final int batchSize;
private final LockMode lockMode;
private final LockOptions lockOptions;
public QueryBuildingParametersImpl(
LoadQueryInfluencers loadQueryInfluencers,
int batchSize,
LockMode lockMode,
LockOptions lockOptions) {
this.loadQueryInfluencers = loadQueryInfluencers;
this.batchSize = batchSize;
this.lockMode = lockMode;
this.lockOptions = lockOptions;
}
@Override
public LoadQueryInfluencers getQueryInfluencers() {
return loadQueryInfluencers;
}
@Override
public int getBatchSize() {
return batchSize;
}
@Override
public LockMode getLockMode() {
return lockMode;
}
@Override
public LockOptions getLockOptions() {
return lockOptions;
}
}

View File

@ -76,7 +76,7 @@ public final class FetchStrategyHelper {
* @param type The association type
* @param sessionFactory The session factory
*
* @return
* @return the fetch style
*/
public static FetchStyle determineFetchStyleByMetadata(
FetchMode mappingFetchMode,
@ -90,15 +90,14 @@ public final class FetchStrategyHelper {
return FetchStyle.JOIN;
}
if ( mappingFetchMode == FetchMode.SELECT ) {
return FetchStyle.SELECT;
}
if ( type.isEntityType() ) {
EntityPersister persister = (EntityPersister) type.getAssociatedJoinable( sessionFactory );
if ( persister.isBatchLoadable() ) {
return FetchStyle.BATCH;
}
else if ( mappingFetchMode == FetchMode.SELECT ) {
return FetchStyle.SELECT;
}
else if ( !persister.hasProxy() ) {
return FetchStyle.JOIN;
}
@ -113,7 +112,6 @@ public final class FetchStrategyHelper {
return FetchStyle.BATCH;
}
}
return FetchStyle.SELECT;
}

View File

@ -0,0 +1,241 @@
/*
* 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.fetchstrategyhelper;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.ElementCollection;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.junit.Test;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.persister.walking.internal.FetchStrategyHelper;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.type.AssociationType;
import static org.junit.Assert.assertSame;
/**
* @author Gail Badner
*/
public class BatchFetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
@Test
public void testManyToOneDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityDefault" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
// batch size is ignored with org.hibernate.FetchMode.JOIN
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
// batch size is ignored with org.hibernate.FetchMode.JOIN
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntitySelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntitySelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.BATCH, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsDefault" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.BATCH, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
// batch size is ignored with org.hibernate.FetchMode.JOIN
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testCollectionSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsSelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsSelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.BATCH, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionSubselectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsSubselect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsSubselect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
// Batch size is ignored with FetchMode.SUBSELECT
assertSame( FetchStyle.SUBSELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
private org.hibernate.FetchMode determineFetchMode(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return entityPersister.getFetchMode( index );
}
private AssociationType determineAssociationType(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return (AssociationType) entityPersister.getSubclassPropertyType( index );
}
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
AnEntity.class,
OtherEntity.class
};
}
@javax.persistence.Entity
@Table(name="entity")
public static class AnEntity {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private OtherEntity otherEntityDefault;
@ManyToOne
@Fetch(FetchMode.JOIN)
private OtherEntity otherEntityJoin;
@ManyToOne
@Fetch(FetchMode.SELECT)
private OtherEntity otherEntitySelect;
// @Fetch(FetchMode.SUBSELECT) is not allowed for ToOne associations
@ElementCollection
@BatchSize( size = 5)
private Set<String> colorsDefault = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.JOIN)
@BatchSize( size = 5)
private Set<String> colorsJoin = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.SELECT)
@BatchSize( size = 5)
private Set<String> colorsSelect = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.SUBSELECT)
@BatchSize( size = 5)
private Set<String> colorsSubselect = new HashSet<String>();
}
@javax.persistence.Entity
@Table(name="otherentity")
@BatchSize( size = 5)
public static class OtherEntity {
@Id
@GeneratedValue
private Long id;
}
}

View File

@ -0,0 +1,232 @@
/*
* 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.fetchstrategyhelper;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.ElementCollection;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.junit.Test;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.persister.walking.internal.FetchStrategyHelper;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.type.AssociationType;
import static org.junit.Assert.assertSame;
/**
* @author Gail Badner
*/
public class FetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
@Test
public void testManyToOneDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityDefault" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntitySelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntitySelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsDefault" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testCollectionSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsSelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsSelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
@Test
public void testCollectionSubselectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "colorsSubselect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "colorsSubselect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SUBSELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.DELAYED, fetchTiming );
}
private org.hibernate.FetchMode determineFetchMode(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return entityPersister.getFetchMode( index );
}
private AssociationType determineAssociationType(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return (AssociationType) entityPersister.getSubclassPropertyType( index );
}
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
AnEntity.class,
OtherEntity.class
};
}
@javax.persistence.Entity
@Table(name="entity")
public static class AnEntity {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private OtherEntity otherEntityDefault;
@ManyToOne
@Fetch(FetchMode.JOIN)
private OtherEntity otherEntityJoin;
@ManyToOne
@Fetch(FetchMode.SELECT)
private OtherEntity otherEntitySelect;
// @Fetch(FetchMode.SUBSELECT) is not allowed for ToOne associations
@ElementCollection
private Set<String> colorsDefault = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.JOIN)
private Set<String> colorsJoin = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.SELECT)
private Set<String> colorsSelect = new HashSet<String>();
@ElementCollection
@Fetch(FetchMode.SUBSELECT)
private Set<String> colorsSubselect = new HashSet<String>();
}
@javax.persistence.Entity
@Table(name="otherentity")
public static class OtherEntity {
@Id
@GeneratedValue
private Long id;
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.fetchstrategyhelper;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.junit.Test;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Proxy;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.persister.walking.internal.FetchStrategyHelper;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.type.AssociationType;
import static org.junit.Assert.assertSame;
/**
* @author Gail Badner
*/
public class NoProxyFetchStrategyHelperTest extends BaseCoreFunctionalTestCase {
@Test
public void testManyToOneDefaultFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityDefault" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityDefault" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneJoinFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntityJoin" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntityJoin" );
assertSame( org.hibernate.FetchMode.JOIN, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.JOIN, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
@Test
public void testManyToOneSelectFetch() {
final AssociationType associationType = determineAssociationType( AnEntity.class, "otherEntitySelect" );
final org.hibernate.FetchMode fetchMode = determineFetchMode( AnEntity.class, "otherEntitySelect" );
assertSame( org.hibernate.FetchMode.SELECT, fetchMode );
final FetchStyle fetchStyle = FetchStrategyHelper.determineFetchStyleByMetadata(
fetchMode,
associationType,
sessionFactory()
);
assertSame( FetchStyle.SELECT, fetchStyle );
final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming(
fetchStyle,
associationType,
sessionFactory()
);
// Proxies are not allowed, so it should be FetchTiming.IMMEDIATE
assertSame( FetchTiming.IMMEDIATE, fetchTiming );
}
private org.hibernate.FetchMode determineFetchMode(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return entityPersister.getFetchMode( index );
}
private AssociationType determineAssociationType(Class<?> entityClass, String path) {
OuterJoinLoadable entityPersister = (OuterJoinLoadable) sessionFactory().getEntityPersister( entityClass.getName() );
int index = ( (UniqueKeyLoadable) entityPersister ).getPropertyIndex( path );
return (AssociationType) entityPersister.getSubclassPropertyType( index );
}
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
AnEntity.class,
OtherEntity.class
};
}
@javax.persistence.Entity
@Table(name="entity")
public static class AnEntity {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private OtherEntity otherEntityDefault;
@ManyToOne
@Fetch(FetchMode.JOIN)
private OtherEntity otherEntityJoin;
@ManyToOne
@Fetch(FetchMode.SELECT)
private OtherEntity otherEntitySelect;
// @Fetch(FetchMode.SUBSELECT) is not allowed for ToOne associations
}
@javax.persistence.Entity
@Table(name="otherentity")
@Proxy(lazy = false)
public static class OtherEntity {
@Id
@GeneratedValue
private Long id;
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.loader.entity.EntityJoinWalker;
import org.hibernate.loader.plan.build.internal.FetchStyleLoadPlanBuildingAssociationVisitationStrategy;
import org.hibernate.loader.plan.build.spi.MetamodelDrivenLoadPlanBuilder;
import org.hibernate.loader.plan.exec.internal.BatchingLoadQueryDetailsFactory;
import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl;
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
import org.hibernate.loader.plan.exec.spi.LoadQueryDetails;
import org.hibernate.loader.plan.spi.LoadPlan;
@ -66,27 +67,14 @@ public class LoadPlanStructureAssertionHelper {
LoadQueryDetails details = BatchingLoadQueryDetailsFactory.INSTANCE.makeEntityLoadQueryDetails(
plan,
persister.getKeyColumnNames(),
new QueryBuildingParameters() {
@Override
public LoadQueryInfluencers getQueryInfluencers() {
return influencers;
}
new QueryBuildingParametersImpl(
influencers,
batchSize,
lockMode,
null
@Override
public int getBatchSize() {
return batchSize;
}
@Override
public LockMode getLockMode() {
return lockMode;
}
@Override
public LockOptions getLockOptions() {
return null;
}
}, sf
),
sf
);
compare( walker, details );

View File

@ -13,6 +13,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.plan.build.internal.FetchStyleLoadPlanBuildingAssociationVisitationStrategy;
import org.hibernate.loader.plan.build.spi.MetamodelDrivenLoadPlanBuilder;
import org.hibernate.loader.plan.exec.internal.BatchingLoadQueryDetailsFactory;
import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl;
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
import org.hibernate.loader.plan.exec.spi.LoadQueryDetails;
import org.hibernate.loader.plan.spi.LoadPlan;
@ -27,14 +28,21 @@ public class Helper implements QueryBuildingParameters {
*/
public static final Helper INSTANCE = new Helper();
private static final QueryBuildingParameters queryBuildingParameters = new QueryBuildingParametersImpl(
LoadQueryInfluencers.NONE,
1,
LockMode.NONE,
null
);
private Helper() {
}
public LoadPlan buildLoadPlan(SessionFactoryImplementor sf, EntityPersister entityPersister) {
final FetchStyleLoadPlanBuildingAssociationVisitationStrategy strategy = new FetchStyleLoadPlanBuildingAssociationVisitationStrategy(
sf,
LoadQueryInfluencers.NONE,
LockMode.NONE
queryBuildingParameters.getQueryInfluencers(),
queryBuildingParameters.getLockMode()
);
return MetamodelDrivenLoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister );
}
@ -50,28 +58,28 @@ public class Helper implements QueryBuildingParameters {
return BatchingLoadQueryDetailsFactory.INSTANCE.makeEntityLoadQueryDetails(
loadPlan,
null,
this,
queryBuildingParameters,
sf
);
}
@Override
public LoadQueryInfluencers getQueryInfluencers() {
return LoadQueryInfluencers.NONE;
return queryBuildingParameters.getQueryInfluencers();
}
@Override
public int getBatchSize() {
return 1;
return queryBuildingParameters.getBatchSize();
}
@Override
public LockMode getLockMode() {
return null;
return queryBuildingParameters.getLockMode();
}
@Override
public LockOptions getLockOptions() {
return null;
return queryBuildingParameters.getLockOptions();
}
}

View File

@ -0,0 +1,437 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2006-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.subselectfetch;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.junit.Test;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Stephen Fikes
* @author Gail Badner
*/
public class SubselectFetchCollectionFromBatchTest extends BaseCoreFunctionalTestCase {
@Override
public void configure(Configuration cfg) {
cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
}
@Test
@TestForIssue( jiraKey = "HHH-10679")
public void testSubselectFetchFromEntityBatch() {
Session s = openSession();
Transaction t = s.beginTransaction();
EmployeeGroup group1 = new EmployeeGroup();
Employee employee1 = new Employee("Jane");
Employee employee2 = new Employee("Jeff");
group1.addEmployee(employee1);
group1.addEmployee(employee2);
EmployeeGroup group2 = new EmployeeGroup();
Employee employee3 = new Employee("Joan");
Employee employee4 = new Employee("John");
group2.addEmployee(employee3);
group2.addEmployee( employee4 );
s.save( group1 );
s.save( group2 );
s.flush();
s.clear();
sessionFactory().getStatistics().clear();
EmployeeGroup[] groups = new EmployeeGroup[] {
(EmployeeGroup) s.load(EmployeeGroup.class, group1.getId()),
(EmployeeGroup) s.load(EmployeeGroup.class, group2.getId())
};
// groups should only contain proxies
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
for (EmployeeGroup group : groups) {
assertFalse( Hibernate.isInitialized( group ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
for (int i = 0 ; i < groups.length; i++ ) {
// Both groups get initialized and are added to the PersistenceContext when i == 0;
// Still need to call Hibernate.initialize( groups[i] ) for i > 0 so that the entity
// in the PersistenceContext gets assigned to its respective proxy target (is this a
// bug???)
Hibernate.initialize( groups[ i ] );
assertTrue( Hibernate.isInitialized( groups[i] ) );
// the collections should be uninitialized
assertFalse( Hibernate.isInitialized( groups[i].getEmployees() ) );
}
// both Group proxies should have been loaded in the same batch;
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group ) );
assertFalse( Hibernate.isInitialized( group.getEmployees() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
// now initialize the collection in the first; collections in both groups
// should get initialized
Hibernate.initialize( groups[0].getEmployees() );
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
// all collections should be initialized now
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group.getEmployees() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
t.rollback();
s.close();
}
@Test
public void testSubselectFetchFromQueryList() {
Session s = openSession();
Transaction t = s.beginTransaction();
EmployeeGroup group1 = new EmployeeGroup();
Employee employee1 = new Employee("Jane");
Employee employee2 = new Employee("Jeff");
group1.addEmployee(employee1);
group1.addEmployee(employee2);
EmployeeGroup group2 = new EmployeeGroup();
Employee employee3 = new Employee("Joan");
Employee employee4 = new Employee("John");
group2.addEmployee(employee3);
group2.addEmployee( employee4 );
s.save( group1 );
s.save( group2 );
s.flush();
s.clear();
sessionFactory().getStatistics().clear();
List<EmployeeGroup> results = s.createQuery(
"from SubselectFetchCollectionFromBatchTest$EmployeeGroup where id in :groups"
).setParameterList(
"groups",
new Long[] { group1.getId(), group2.getId() }
).list();
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
for (EmployeeGroup group : results) {
assertTrue( Hibernate.isInitialized( group ) );
assertFalse( Hibernate.isInitialized( group.getEmployees() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
// now initialize the collection in the first; collections in both groups
// should get initialized
Hibernate.initialize( results.get( 0 ).getEmployees() );
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
// all collections should be initialized now
for (EmployeeGroup group : results) {
assertTrue( Hibernate.isInitialized( group.getEmployees() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
t.rollback();
s.close();
}
@Test
@TestForIssue( jiraKey = "HHH-10679")
public void testMultiSubselectFetchSamePersisterQueryList() {
Session s = openSession();
Transaction t = s.beginTransaction();
EmployeeGroup group1 = new EmployeeGroup();
Employee employee1 = new Employee("Jane");
Employee employee2 = new Employee("Jeff");
group1.addEmployee( employee1 );
group1.addEmployee( employee2 );
group1.setManager( new Employee( "group1 manager" ) );
group1.getManager().addCollaborator( new Employee( "group1 manager's collaborator#1" ) );
group1.getManager().addCollaborator( new Employee( "group1 manager's collaborator#2" ) );
group1.setLead( new Employee( "group1 lead" ) );
group1.getLead().addCollaborator( new Employee( "group1 lead's collaborator#1" ) );
EmployeeGroup group2 = new EmployeeGroup();
Employee employee3 = new Employee("Joan");
Employee employee4 = new Employee("John");
group2.addEmployee( employee3 );
group2.addEmployee( employee4 );
group2.setManager( new Employee( "group2 manager" ) );
group2.getManager().addCollaborator( new Employee( "group2 manager's collaborator#1" ) );
group2.getManager().addCollaborator( new Employee( "group2 manager's collaborator#2" ) );
group2.getManager().addCollaborator( new Employee( "group2 manager's collaborator#3" ) );
group2.setLead( new Employee( "group2 lead" ) );
group2.getLead().addCollaborator( new Employee( "group2 lead's collaborator#1" ) );
group2.getLead().addCollaborator( new Employee( "group2 lead's collaborator#2" ) );
s.save( group1 );
s.save( group2 );
s.flush();
s.clear();
sessionFactory().getStatistics().clear();
EmployeeGroup[] groups = new EmployeeGroup[] {
(EmployeeGroup) s.load(EmployeeGroup.class, group1.getId()),
(EmployeeGroup) s.load(EmployeeGroup.class, group2.getId())
};
// groups should only contain proxies
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
for (EmployeeGroup group : groups) {
assertFalse( Hibernate.isInitialized( group ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
for (int i = 0 ; i < groups.length; i++ ) {
// Both groups get initialized and are added to the PersistenceContext when i == 0;
// Still need to call Hibernate.initialize( groups[i] ) for i > 0 so that the entity
// in the PersistenceContext gets assigned to its respective proxy target (is this a
// bug???)
Hibernate.initialize( groups[ i ] );
assertTrue( Hibernate.isInitialized( groups[i] ) );
assertTrue( Hibernate.isInitialized( groups[i].getLead() ) );
assertFalse( Hibernate.isInitialized( groups[i].getLead().getCollaborators() ) );
assertTrue( Hibernate.isInitialized( groups[i].getManager() ) );
assertFalse( Hibernate.isInitialized( groups[i].getManager().getCollaborators() ) );
// the collections should be uninitialized
assertFalse( Hibernate.isInitialized( groups[i].getEmployees() ) );
}
// both Group proxies should have been loaded in the same batch;
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group ) );
assertFalse( Hibernate.isInitialized( group.getEmployees() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
// now initialize the collection in the first; collections in both groups
// should get initialized
Hibernate.initialize( groups[0].getEmployees() );
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
// all EmployeeGroup#employees should be initialized now
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group.getEmployees() ) );
assertFalse( Hibernate.isInitialized( group.getLead().getCollaborators() ) );
assertFalse( Hibernate.isInitialized( group.getManager().getCollaborators() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
// now initialize groups[0].getLead().getCollaborators();
// groups[1].getLead().getCollaborators() should also be initialized
Hibernate.initialize( groups[0].getLead().getCollaborators() );
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group.getLead().getCollaborators() ) );
assertFalse( Hibernate.isInitialized( group.getManager().getCollaborators() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
// now initialize groups[0].getManager().getCollaborators();
// groups[1].getManager().getCollaborators() should also be initialized
Hibernate.initialize( groups[0].getManager().getCollaborators() );
assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() );
sessionFactory().getStatistics().clear();
for (EmployeeGroup group : groups) {
assertTrue( Hibernate.isInitialized( group.getManager().getCollaborators() ) );
}
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
assertEquals( group1.getLead().getCollaborators().size(), groups[0].getLead().getCollaborators().size() );
assertEquals( group2.getLead().getCollaborators().size(), groups[1].getLead().getCollaborators().size() );
assertEquals( group1.getManager().getCollaborators().size(), groups[0].getManager().getCollaborators().size() );
assertEquals( group2.getManager().getCollaborators().size(), groups[1].getManager().getCollaborators().size() );
assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() );
t.rollback();
s.close();
}
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { EmployeeGroup.class, Employee.class };
}
@Entity
@Table(name = "EmployeeGroup")
@BatchSize(size = 1000)
private static class EmployeeGroup {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Employee manager;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Employee lead;
@OneToMany(cascade = CascadeType.ALL)
@Fetch(FetchMode.SUBSELECT)
@JoinTable(name="EmployeeGroup_employees")
private List<Employee> employees = new ArrayList<Employee>();
public EmployeeGroup(long id) {
this.id = id;
}
@SuppressWarnings("unused")
protected EmployeeGroup() {
}
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
public Employee getLead() {
return lead;
}
public void setLead(Employee lead) {
this.lead = lead;
}
public boolean addEmployee(Employee employee) {
return employees.add(employee);
}
public List<Employee> getEmployees() {
return employees;
}
public long getId() {
return id;
}
@Override
public String toString() {
return id.toString();
}
}
@Entity
@Table(name = "Employee")
private static class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL)
@Fetch(FetchMode.SUBSELECT)
private List<Employee> collaborators = new ArrayList<Employee>();
public String getName() {
return name;
}
@SuppressWarnings("unused")
private Employee() {
}
public Employee(String name) {
this.name = name;
}
public boolean addCollaborator(Employee employee) {
return collaborators.add(employee);
}
public List<Employee> getCollaborators() {
return collaborators;
}
@Override
public String toString() {
return name;
}
}
}