Throw MultipleBagFetchException when trying to fetch multiple bags

This commit is contained in:
Andrea Boriero 2020-07-21 13:37:59 +01:00
parent fc914ea647
commit 842c4f18c9
17 changed files with 619 additions and 618 deletions

View File

@ -12,15 +12,14 @@ import java.util.Iterator;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.collection.spi.BagSemantics;
import org.hibernate.collection.spi.CollectionInitializerProducer;
import org.hibernate.collection.spi.CollectionSemantics;
import org.hibernate.engine.FetchTiming;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.collection.internal.BagInitializerProducer;
import org.hibernate.sql.results.graph.DomainResultCreationState;
@ -29,7 +28,7 @@ import org.hibernate.sql.results.graph.FetchParent;
/**
* @author Steve Ebersole
*/
public abstract class AbstractBagSemantics<B extends Collection<?>> implements CollectionSemantics<B> {
public abstract class AbstractBagSemantics<B extends Collection<?>> implements BagSemantics<B> {
@Override
public Class<B> getCollectionJavaType() {
//noinspection unchecked

View File

@ -0,0 +1,14 @@
/*
* 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.collection.spi;
/**
* @author Andrea Boriero
*/
public interface BagSemantics<B> extends CollectionSemantics<B> {
}

View File

@ -16,6 +16,7 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.collection.spi.BagSemantics;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.profile.FetchProfile;
@ -27,6 +28,7 @@ import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.internal.FilterHelper;
import org.hibernate.internal.util.MutableInteger;
import org.hibernate.loader.MultipleBagFetchException;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.Loader;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
@ -43,6 +45,7 @@ import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
@ -443,7 +446,8 @@ public class LoaderSelectBuilder {
}
final List<Fetch> fetches = new ArrayList<>();
final BiConsumer<Fetchable, Boolean> processor = createFetchableBiConsumer( fetchParent, querySpec, creationState, fetches );
final List<String> bagRoles = new ArrayList<>();
final BiConsumer<Fetchable, Boolean> processor = createFetchableBiConsumer( fetchParent, querySpec, creationState, fetches, bagRoles );
final FetchableContainer referencedMappingContainer = fetchParent.getReferencedMappingContainer();
if ( fetchParent.getNavigablePath().getParent() != null ) {
@ -452,6 +456,9 @@ public class LoaderSelectBuilder {
}
referencedMappingContainer.visitFetchables(
fetchable -> processor.accept( fetchable, false ), null );
if ( bagRoles.size() > 1 ) {
throw new MultipleBagFetchException( bagRoles );
}
return fetches;
}
@ -459,11 +466,12 @@ public class LoaderSelectBuilder {
FetchParent fetchParent,
QuerySpec querySpec,
LoaderSqlAstCreationState creationState,
List<Fetch> fetches) {
List<Fetch> fetches,
List<String> bagRoles) {
return (fetchable, isKeyFetchable) -> {
NavigablePath panrentNavigablePath = fetchParent.getNavigablePath();
if ( isKeyFetchable ) {
panrentNavigablePath = panrentNavigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME );
panrentNavigablePath = new EntityIdentifierNavigablePath( panrentNavigablePath );
}
final NavigablePath fetchablePath = panrentNavigablePath.append( fetchable.getFetchableName() );
@ -542,23 +550,31 @@ public class LoaderSelectBuilder {
null,
creationState
);
fetches.add( fetch );
if ( fetchable instanceof PluralAttributeMapping && fetchTiming == FetchTiming.IMMEDIATE && joined ) {
final TableGroup joinTableGroup = creationState.getFromClauseAccess().getTableGroup( fetchablePath );
if ( fetch.getTiming() == FetchTiming.IMMEDIATE && fetchable instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
applyFiltering(
querySpec,
joinTableGroup,
pluralAttributeMapping
);
applyOrdering(
querySpec,
fetchablePath,
pluralAttributeMapping,
creationState
);
if ( pluralAttributeMapping.getMappedTypeDescriptor()
.getCollectionSemantics() instanceof BagSemantics ) {
bagRoles.add( fetchable.getNavigableRole().getNavigableName() );
}
if ( joined ) {
final TableGroup joinTableGroup = creationState.getFromClauseAccess()
.getTableGroup( fetchablePath );
applyFiltering(
querySpec,
joinTableGroup,
pluralAttributeMapping
);
applyOrdering(
querySpec,
fetchablePath,
pluralAttributeMapping,
creationState
);
}
}
fetches.add( fetch );
}
finally {
if ( changeFetchDepth ) {

View File

@ -48,7 +48,16 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
public void prepare() {
// see `org.hibernate.persister.entity.AbstractEntityPersister#createLoaders`
// we should pre-load a few - maybe LockMode.NONE and LockMode.READ
final LockOptions lockOptions = LockOptions.NONE;
final LoadQueryInfluencers queryInfluencers = new LoadQueryInfluencers( sessionFactory );
final SingleIdLoadPlan<T> plan = createLoadPlan(
lockOptions,
queryInfluencers,
sessionFactory
);
if ( determineIfReusable( lockOptions, queryInfluencers ) ) {
selectByLockMode.put( lockOptions.getLockMode(), plan );
}
}
@Override

View File

@ -16,7 +16,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
*/
public abstract class SingleIdEntityLoaderSupport<T> implements SingleIdEntityLoader<T> {
private final EntityMappingType entityDescriptor;
private final SessionFactoryImplementor sessionFactory;
protected final SessionFactoryImplementor sessionFactory;
private DatabaseSnapshotExecutor databaseSnapshotExecutor;

View File

@ -0,0 +1,242 @@
/*
* 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.metamodel.internal;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Andrea Boriero
*/
public abstract class AbstractCompositeIdentifierMapping
implements CompositeIdentifierMapping, EmbeddableValuedFetchable, FetchOptions {
private final NavigableRole navigableRole;
private final String tableExpression;
private final StateArrayContributorMetadataAccess attributeMetadataAccess;
private final List<String> columnNames;
private final EntityMappingType entityMapping;
private final EmbeddableMappingType embeddableDescriptor;
private final SessionFactoryImplementor sessionFactory;
public AbstractCompositeIdentifierMapping(
StateArrayContributorMetadataAccess attributeMetadataAccess,
EmbeddableMappingType embeddableDescriptor,
EntityMappingType entityMapping,
String tableExpression,
String[] columnNames,
SessionFactoryImplementor sessionFactory) {
this.attributeMetadataAccess = attributeMetadataAccess;
this.embeddableDescriptor = embeddableDescriptor;
this.entityMapping = entityMapping;
this.tableExpression = tableExpression;
this.sessionFactory = sessionFactory;
this.columnNames = Arrays.asList( columnNames );
this.navigableRole = entityMapping.getNavigableRole()
.appendContainer( EntityIdentifierMapping.ROLE_LOCAL_NAME );
}
@Override
public EmbeddableMappingType getMappedTypeDescriptor() {
return embeddableDescriptor;
}
@Override
public EmbeddableMappingType getPartMappingType() {
return getEmbeddableTypeDescriptor();
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return getEmbeddableTypeDescriptor().getMappedJavaTypeDescriptor();
}
@Override
public EmbeddableMappingType getEmbeddableTypeDescriptor() {
return embeddableDescriptor;
}
@Override
public String getContainingTableExpression() {
return tableExpression;
}
@Override
public NavigableRole getNavigableRole() {
return navigableRole;
}
@Override
public List<String> getMappedColumnExpressions() {
return columnNames;
}
@Override
public void visitColumns(ColumnConsumer consumer) {
getAttributes().forEach(
attribute -> {
if ( attribute instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping associationAttributeMapping = (ToOneAttributeMapping) attribute;
associationAttributeMapping.getForeignKeyDescriptor().visitReferringColumns( consumer );
}
else {
attribute.visitColumns( consumer );
}
}
);
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableFetchImpl(
fetchablePath,
this,
fetchParent,
fetchTiming,
selected,
attributeMetadataAccess.resolveAttributeMetadata( null ).isNullable(),
creationState
);
}
@Override
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
final CompositeTableGroup compositeTableGroup = new CompositeTableGroup(
navigablePath,
this,
lhs
);
final TableGroupJoin join = new TableGroupJoin( navigablePath, SqlAstJoinType.LEFT, compositeTableGroup, null );
lhs.addTableGroupJoin( join );
return join;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return embeddableDescriptor.findSubPart( name, treatTargetType );
}
@Override
public void visitSubParts(
Consumer<ModelPart> consumer,
EntityMappingType treatTargetType) {
embeddableDescriptor.visitSubParts( consumer, treatTargetType );
}
@Override
public void visitJdbcTypes(
Consumer<JdbcMapping> action,
Clause clause,
TypeConfiguration typeConfiguration) {
embeddableDescriptor.visitJdbcTypes( action, clause, typeConfiguration );
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableResultImpl<>(
navigablePath,
this,
resultVariable,
creationState
);
}
@Override
public Object instantiate() {
return getEntityMapping().getRepresentationStrategy().getInstantiator().instantiate( sessionFactory );
}
@Override
public SingularAttributeMapping getParentInjectionAttributeMapping() {
return null;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return entityMapping;
}
@Override
public FetchOptions getMappedFetchOptions() {
return this;
}
@Override
public FetchStyle getStyle() {
return FetchStyle.JOIN;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
protected EntityMappingType getEntityMapping() {
return entityMapping;
}
}

View File

@ -28,6 +28,9 @@ public interface PluralAttributeMapping
CollectionPart getIndexDescriptor();
@Override
CollectionMappingType getMappedTypeDescriptor();
interface IndexMetadata {
CollectionPart getIndexDescriptor();
int getListIndexBase();

View File

@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
@ -187,9 +188,14 @@ public class BasicValuedCollectionPart
nature.getName()
);
NavigablePath parentNavigablePath = fetchablePath.getParent();
if ( parentNavigablePath instanceof EntityIdentifierNavigablePath ) {
parentNavigablePath = parentNavigablePath.getParent();
}
final TableGroup tableGroup = creationState.getSqlAstCreationState()
.getFromClauseAccess()
.findTableGroup( fetchablePath.getParent() );
.findTableGroup( parentNavigablePath );
final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState );
return new BasicFetch(

View File

@ -6,102 +6,62 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.internal.AbstractCompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Support for {@link javax.persistence.EmbeddedId}
*
* @author Andrea Boriero
*/
public class EmbeddedIdentifierMappingImpl
implements CompositeIdentifierMapping, EmbeddableValuedFetchable, FetchOptions {
private final NavigableRole navigableRole;
private final EntityMappingType entityMapping;
public class EmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping {
private final String name;
private final MappingType type;
private final StateArrayContributorMetadataAccess attributeMetadataAccess;
private final PropertyAccess propertyAccess;
private final String tableExpression;
private final String[] attrColumnNames;
private final SessionFactoryImplementor sessionFactory;
@SuppressWarnings("WeakerAccess")
public EmbeddedIdentifierMappingImpl(
EntityMappingType entityMapping,
String name,
MappingType type,
EmbeddableMappingType embeddableDescriptor,
StateArrayContributorMetadataAccess attributeMetadataAccess,
PropertyAccess propertyAccess,
String tableExpression,
String[] attrColumnNames,
SessionFactoryImplementor sessionFactory) {
this.navigableRole = entityMapping.getNavigableRole().appendContainer( EntityIdentifierMapping.ROLE_LOCAL_NAME );
this.entityMapping = entityMapping;
super(
attributeMetadataAccess,
embeddableDescriptor,
entityMapping,
tableExpression,
attrColumnNames,
sessionFactory
);
this.name = name;
this.type = type;
this.attributeMetadataAccess = attributeMetadataAccess;
this.propertyAccess = propertyAccess;
this.tableExpression = tableExpression;
this.attrColumnNames = attrColumnNames;
this.sessionFactory = sessionFactory;
}
@Override
public EmbeddableMappingType getPartMappingType() {
return (EmbeddableMappingType) type;
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return getMappedTypeDescriptor().getMappedJavaTypeDescriptor();
}
@Override
@ -109,11 +69,6 @@ public class EmbeddedIdentifierMappingImpl
return name;
}
@Override
public NavigableRole getNavigableRole() {
return navigableRole;
}
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
if ( entity instanceof HibernateProxy ) {
@ -127,44 +82,6 @@ public class EmbeddedIdentifierMappingImpl
propertyAccess.getSetter().set( entity, id, session.getFactory() );
}
@Override
public Object instantiate() {
return entityMapping.getRepresentationStrategy().getInstantiator().instantiate( sessionFactory );
}
@Override
public EmbeddableMappingType getEmbeddableTypeDescriptor() {
return getMappedTypeDescriptor();
}
@Override
public EmbeddableMappingType getMappedTypeDescriptor() {
return (EmbeddableMappingType) type;
}
@Override
public String getContainingTableExpression() {
return tableExpression;
}
@Override
public List<String> getMappedColumnExpressions() {
return Arrays.asList( attrColumnNames );
}
@Override
public SingularAttributeMapping getParentInjectionAttributeMapping() {
return null;
}
@Override
public void visitJdbcTypes(
Consumer<JdbcMapping> action,
Clause clause,
TypeConfiguration typeConfiguration) {
getMappedTypeDescriptor().visitJdbcTypes( action,clause,typeConfiguration );
}
@Override
public void visitJdbcValues(
Object value,
@ -189,27 +106,14 @@ public class EmbeddedIdentifierMappingImpl
);
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableResultImpl<>(
navigablePath,
this,
resultVariable,
creationState
);
}
@Override
public Expression toSqlExpression(
TableGroup tableGroup,
Clause clause,
SqmToSqlAstConverter walker,
SqlAstCreationState sqlAstCreationState) {
final List<ColumnReference> columnReferences = CollectionHelper.arrayList( attrColumnNames.length );
final List<String> attrColumnNames = getMappedColumnExpressions();
final List<ColumnReference> columnReferences = CollectionHelper.arrayList( attrColumnNames.size() );
final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() );
getEmbeddableTypeDescriptor().visitJdbcTypes(
new Consumer<JdbcMapping>() {
@ -217,21 +121,22 @@ public class EmbeddedIdentifierMappingImpl
@Override
public void accept(JdbcMapping jdbcMapping) {
final String attrColumnExpr = attrColumnNames[ index++ ];
final String attrColumnExpr = attrColumnNames.get( index++ );
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
attrColumnExpr
),
sqlAstProcessingState -> new ColumnReference(
tableReference.getIdentificationVariable(),
attrColumnExpr,
false,
jdbcMapping,
sqlAstCreationState.getCreationContext().getSessionFactory()
)
);
final Expression columnReference = sqlAstCreationState.getSqlExpressionResolver()
.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
attrColumnExpr
),
sqlAstProcessingState -> new ColumnReference(
tableReference.getIdentificationVariable(),
attrColumnExpr,
false,
jdbcMapping,
sqlAstCreationState.getCreationContext().getSessionFactory()
)
);
columnReferences.add( (ColumnReference) columnReference );
}
@ -243,99 +148,23 @@ public class EmbeddedIdentifierMappingImpl
return new SqlTuple( columnReferences, this );
}
@Override
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
final CompositeTableGroup compositeTableGroup = new CompositeTableGroup(
navigablePath,
this,
lhs
);
final TableGroupJoin join = new TableGroupJoin( navigablePath, SqlAstJoinType.LEFT, compositeTableGroup, null );
lhs.addTableGroupJoin( join );
return join;
}
@Override
public String getSqlAliasStem() {
return name;
return name;
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return getMappedTypeDescriptor().findSubPart( name, treatTargetType );
}
@Override
public void visitSubParts(
Consumer<ModelPart> consumer,
EntityMappingType treatTargetType) {
getMappedTypeDescriptor().visitSubParts( consumer, treatTargetType );
}
@Override
public String getFetchableName() {
return name;
}
@Override
public FetchOptions getMappedFetchOptions() {
return this;
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableFetchImpl(
fetchablePath,
this,
fetchParent,
fetchTiming,
selected,
attributeMetadataAccess.resolveAttributeMetadata( null ).isNullable(),
creationState
);
}
@Override
public int getNumberOfFetchables() {
return getEmbeddableTypeDescriptor().getNumberOfAttributeMappings();
}
@Override
public void visitColumns(ColumnConsumer consumer) {
getAttributes().forEach(
attribute -> {
if ( attribute instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping associationAttributeMapping = (ToOneAttributeMapping) attribute;
associationAttributeMapping.getForeignKeyDescriptor().visitReferringColumns( consumer );
}
else {
attribute.visitColumns( consumer );
}
}
);
}
@Override
public EntityMappingType findContainingEntityMapping() {
return entityMapping;
}
@Override
public int getAttributeCount() {
@ -348,13 +177,4 @@ public class EmbeddedIdentifierMappingImpl
return (Collection) getEmbeddableTypeDescriptor().getAttributeMappings();
}
@Override
public FetchStyle getStyle() {
return FetchStyle.JOIN;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
}

View File

@ -6,67 +6,34 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Component;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.internal.AbstractCompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.CompositeTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* A "non-aggregated" composite identifier.
*
* <p>
* This is an identifier mapped using JPA's {@link javax.persistence.MapsId} feature.
*
* @apiNote Technically a MapsId id does not have to be composite; we still handle that this class however
*
* @author Steve Ebersole
* @apiNote Technically a MapsId id does not have to be composite; we still handle that this class however
*/
public class NonAggregatedIdentifierMappingImpl
implements CompositeIdentifierMapping, EmbeddableValuedFetchable, FetchOptions {
private final EmbeddableMappingType embeddableDescriptor;
private final NavigableRole navigableRole;
private final EntityMappingType entityMapping;
public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping {
private final List<SingularAttributeMapping> idAttributeMappings;
private final StateArrayContributorMetadataAccess attributeMetadataAccess;
private final String rootTableName;
private final List<String> idColumnNames;
public NonAggregatedIdentifierMappingImpl(
EmbeddableMappingType embeddableDescriptor,
EntityMappingType entityMapping,
@ -78,35 +45,15 @@ public class NonAggregatedIdentifierMappingImpl
Component bootIdClassDescriptor,
MappingModelCreationProcess creationProcess) {
// todo (6.0) : handle MapsId and IdClass
this.embeddableDescriptor = embeddableDescriptor;
this.entityMapping = entityMapping;
super(
attributeMetadataAccess,
embeddableDescriptor,
entityMapping,
rootTableName,
rootTableKeyColumnNames,
creationProcess.getCreationContext().getSessionFactory()
);
this.idAttributeMappings = idAttributeMappings;
this.attributeMetadataAccess = attributeMetadataAccess;
this.rootTableName = rootTableName;
this.idColumnNames = Arrays.asList( rootTableKeyColumnNames );
this.navigableRole = entityMapping.getNavigableRole().appendContainer( EntityIdentifierMapping.ROLE_LOCAL_NAME );
}
@Override
public EntityMappingType getPartMappingType() {
return entityMapping;
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return entityMapping.getJavaTypeDescriptor();
}
@Override
public NavigableRole getNavigableRole() {
return navigableRole;
}
@Override
public EmbeddableMappingType getMappedTypeDescriptor() {
return embeddableDescriptor;
}
@Override
@ -128,56 +75,9 @@ public class NonAggregatedIdentifierMappingImpl
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
// nothing to do
}
@Override
public Object instantiate() {
return entityMapping;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableResultImpl<>(
navigablePath,
this,
resultVariable,
creationState
);
}
@Override
public EntityMappingType findContainingEntityMapping() {
return entityMapping;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EmbeddableValuedFetchable
@Override
public EmbeddableMappingType getEmbeddableTypeDescriptor() {
return getMappedTypeDescriptor();
}
@Override
public String getContainingTableExpression() {
return rootTableName;
}
@Override
public List<String> getMappedColumnExpressions() {
return idColumnNames;
}
@Override
public SingularAttributeMapping getParentInjectionAttributeMapping() {
return null;
}
@Override
public Expression toSqlExpression(
TableGroup tableGroup,
@ -187,85 +87,19 @@ public class NonAggregatedIdentifierMappingImpl
return null;
}
@Override
public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath,
TableGroup lhs,
String explicitSourceAlias,
SqlAstJoinType sqlAstJoinType,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) {
final CompositeTableGroup compositeTableGroup = new CompositeTableGroup(
navigablePath,
this,
lhs
);
final TableGroupJoin join = new TableGroupJoin( navigablePath, SqlAstJoinType.LEFT, compositeTableGroup, null );
lhs.addTableGroupJoin( join );
return join;
}
@Override
public String getSqlAliasStem() {
return "id";
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return getMappedTypeDescriptor().findSubPart( name, treatTargetType );
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
getMappedTypeDescriptor().visitSubParts( consumer, treatTargetType );
}
@Override
public String getFetchableName() {
return "id";
}
@Override
public FetchOptions getMappedFetchOptions() {
return this;
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
return new EmbeddableFetchImpl(
fetchablePath,
this,
fetchParent,
fetchTiming,
selected,
attributeMetadataAccess.resolveAttributeMetadata( null ).isNullable(),
creationState
);
}
@Override
public int getNumberOfFetchables() {
return idAttributeMappings.size();
}
@Override
public FetchStyle getStyle() {
return FetchStyle.JOIN;
}
@Override
public FetchTiming getTiming() {
return FetchTiming.IMMEDIATE;
}
}

View File

@ -25,6 +25,7 @@ import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess;
@ -50,8 +51,8 @@ import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl;
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
import org.hibernate.type.ForeignKeyDirection;
/**
@ -227,7 +228,7 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
NavigablePath parentNavigablePath = fetchablePath.getParent();
ModelPart modelPart = creationState.resolveModelPart( parentNavigablePath );
if ( modelPart instanceof EmbeddedIdentifierMappingImpl ) {
while ( parentNavigablePath.getFullPath().endsWith( EntityIdentifierMapping.ROLE_LOCAL_NAME ) ) {
while ( parentNavigablePath instanceof EntityIdentifierNavigablePath ) {
parentNavigablePath = parentNavigablePath.getParent();
}
}
@ -340,7 +341,7 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
}
if ( modelPart instanceof EntityCollectionPart ) {
if ( parentOfParent.getFullPath().endsWith( EntityIdentifierMapping.ROLE_LOCAL_NAME ) ) {
if ( parentOfParent instanceof EntityIdentifierNavigablePath ) {
parentOfParent = parentOfParent.getParent();
}
return ( (PluralAttributeMapping) creationState.resolveModelPart( parentOfParent ) ).isBidirectionalAttributeName(

View File

@ -0,0 +1,24 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
/**
* @author Andrea Boriero
*/
public class EntityIdentifierNavigablePath extends NavigablePath {
public EntityIdentifierNavigablePath(NavigablePath parent) {
super( parent, EntityIdentifierMapping.ROLE_LOCAL_NAME );
}
@Override
public String getLocalName() {
return EntityIdentifierMapping.ROLE_LOCAL_NAME;
}
}

View File

@ -18,6 +18,7 @@ import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.collection.spi.BagSemantics;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -26,6 +27,7 @@ import org.hibernate.internal.FilterHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.loader.MultipleBagFetchException;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
@ -271,6 +273,7 @@ public class StandardSqmSelectTranslator
@Override
public List<Fetch> visitFetches(FetchParent fetchParent) {
final List<Fetch> fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() );
final List<String> bagRoles = new ArrayList<>();
final BiConsumer<Fetchable, Boolean> fetchableBiConsumer = (fetchable, isKeyFetchable) -> {
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
@ -291,6 +294,12 @@ public class StandardSqmSelectTranslator
final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable, isKeyFetchable );
if ( fetch != null ) {
if ( fetch.getTiming() == FetchTiming.IMMEDIATE &&
fetchable instanceof PluralAttributeMapping &&
( (PluralAttributeMapping) fetchable ).getMappedTypeDescriptor()
.getCollectionSemantics() instanceof BagSemantics ) {
bagRoles.add( fetchable.getNavigableRole().getNavigableName() );
}
fetches.add( fetch );
}
}
@ -304,7 +313,9 @@ public class StandardSqmSelectTranslator
// fetchParent.getReferencedMappingContainer().visitFetchables( fetchableBiConsumer, treatTargetType );
fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, true ), null );
fetchParent.getReferencedMappingContainer().visitFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, false ), null );
if ( bagRoles.size() > 1 ) {
throw new MultipleBagFetchException( bagRoles );
}
return fetches;
}

View File

@ -19,6 +19,7 @@ import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.AbstractFetchParent;
@ -71,7 +72,7 @@ public abstract class AbstractEntityResultGraphNode extends AbstractFetchParent
identifierResult = null;
if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) {
identifierMapping.createDomainResult(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
new EntityIdentifierNavigablePath( navigablePath ),
entityTableGroup,
null,
creationState
@ -83,7 +84,7 @@ public abstract class AbstractEntityResultGraphNode extends AbstractFetchParent
}
else {
identifierResult = identifierMapping.createDomainResult(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
new EntityIdentifierNavigablePath( navigablePath ),
entityTableGroup,
null,
creationState
@ -141,7 +142,7 @@ public abstract class AbstractEntityResultGraphNode extends AbstractFetchParent
attributeMapping -> {
if ( attributeMapping instanceof ToOneAttributeMapping ) {
( (ToOneAttributeMapping) attributeMapping ).getForeignKeyDescriptor().createDomainResult(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
new EntityIdentifierNavigablePath( navigablePath ),
entityTableGroup,
null,
creationState
@ -149,7 +150,7 @@ public abstract class AbstractEntityResultGraphNode extends AbstractFetchParent
}
else {
attributeMapping.createDomainResult(
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
new EntityIdentifierNavigablePath( navigablePath ),
entityTableGroup,
null,
creationState

View File

@ -0,0 +1,217 @@
/*
* 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.orm.test.collection.bag;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.loader.MultipleBagFetchException;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@DomainModel(
annotatedClasses = {
MultipleBagFetchHqlTest.Post.class,
MultipleBagFetchHqlTest.PostComment.class,
MultipleBagFetchHqlTest.Tag.class
}
)
@SessionFactory
public class MultipleBagFetchHqlTest {
@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Post post = new Post();
post.setId( 1L );
post.setTitle( String.format( "Post nr. %d", 1 ) );
PostComment comment = new PostComment();
comment.setId( 1L );
comment.setReview( "Excellent!" );
session.persist( post );
session.persist( comment );
post.comments.add( comment );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete from Post" ).executeUpdate();
session.createQuery( "delete from Tag" ).executeUpdate();
session.createQuery( "delete from PostComment" ).executeUpdate();
}
);
}
@Test
public void testMultipleBagFetchHql(SessionFactoryScope scope) {
scope.inSession(
session -> {
try {
session.createQuery(
"select p " +
"from Post p " +
"join fetch p.tags " +
"join fetch p.comments " +
"where p.id = :id"
).setParameter( "id", 1L ).uniqueResult();
fail( "Should throw org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags" );
}
catch (IllegalArgumentException expected) {
session.getTransaction().rollback();
// MultipleBagFetchException was converted to IllegalArgumentException
assertTrue( MultipleBagFetchException.class.isInstance( expected.getCause() ) );
}
}
);
}
@Test
public void testSingleBagFetchHql(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery(
"select p " +
"from Post p " +
"join fetch p.tags " +
"join p.comments " +
"where p.id = :id"
).setParameter( "id", 1L ).uniqueResult();
}
);
}
@Entity(name = "Post")
@Table(name = "post")
public static class Post {
@Id
private Long id;
private String title;
@OneToMany(fetch = FetchType.LAZY)
private List<PostComment> comments = new ArrayList<PostComment>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
public Post() {
}
public Post(Long id) {
this.id = id;
}
public Post(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Tag> getTags() {
return tags;
}
}
@Entity(name = "PostComment")
@Table(name = "post_comment")
public static class PostComment {
@Id
private Long id;
private String review;
public PostComment() {
}
public PostComment(String review) {
this.review = review;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getReview() {
return review;
}
public void setReview(String review) {
this.review = review;
}
}
@Entity(name = "Tag")
@Table(name = "tag")
public static class Tag {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -4,7 +4,7 @@
* 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.collection.bag;
package org.hibernate.orm.test.collection.bag;
import java.util.ArrayList;
import java.util.List;
@ -21,12 +21,11 @@ import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.loader.MultipleBagFetchException;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.fail;
public class MultipleBagFetchTest {

View File

@ -1,195 +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.test.collection.bag;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.loader.MultipleBagFetchException;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MultipleBagFetchHqlTest extends BaseCoreFunctionalTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
Post.class,
PostComment.class,
Tag.class
};
}
@Test
public void testMultipleBagFetchHql() throws Exception {
Session session = openSession();
Transaction transaction = session.beginTransaction();
Post post = new Post();
post.setId( 1L );
post.setTitle( String.format( "Post nr. %d", 1 ) );
PostComment comment = new PostComment();
comment.setId(1L);
comment.setReview( "Excellent!" );
session.persist(post);
session.persist( comment );
post.comments.add( comment );
transaction.commit();
session.close();
session = openSession();
session.beginTransaction();
try {
post = (Post) session.createQuery(
"select p " +
"from Post p " +
"join fetch p.tags " +
"join fetch p.comments " +
"where p.id = :id"
)
.setParameter( "id", 1L )
.uniqueResult();
fail("Should throw org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags");
}
catch ( IllegalArgumentException expected ) {
session.getTransaction().rollback();
// MultipleBagFetchException was converted to IllegalArgumentException
assertTrue( MultipleBagFetchException.class.isInstance( expected.getCause() ) );
}
finally {
session.close();
}
}
@Entity(name = "Post")
@Table(name = "post")
public static class Post {
@Id
private Long id;
private String title;
@OneToMany(fetch = FetchType.LAZY)
private List<PostComment> comments = new ArrayList<PostComment>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<Tag>();
public Post() {
}
public Post(Long id) {
this.id = id;
}
public Post(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Tag> getTags() {
return tags;
}
}
@Entity(name = "PostComment")
@Table(name = "post_comment")
public static class PostComment {
@Id
private Long id;
private String review;
public PostComment() {
}
public PostComment(String review) {
this.review = review;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getReview() {
return review;
}
public void setReview(String review) {
this.review = review;
}
}
@Entity(name = "Tag")
@Table(name = "tag")
public static class Tag {
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}