HHH-16019 - @Where not consistently applied across association boundaries
HHH-16264 - Deprecate `hibernate.use_entity_where_clause_for_collections` HHH-16265 - Remove `@Where#applyInToManyFetch`
This commit is contained in:
parent
6685f2c89e
commit
0d6fc46ee0
|
@ -49,13 +49,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
* </pre>
|
||||
* <p>
|
||||
* By default, {@code @Where} restrictions declared for an entity are
|
||||
* applied when loading a collection of that entity type. This behavior is
|
||||
* controlled by:
|
||||
* <ol>
|
||||
* <li>the annotation member {@link #applyInToManyFetch()}, and
|
||||
* <li>the configuration property
|
||||
* {@value org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS}.
|
||||
* </ol>
|
||||
* applied when loading associations of that entity type. This behavior can
|
||||
* be disabled using the setting {@value org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS};
|
||||
* note, however, that setting is disabled.
|
||||
* <p>
|
||||
* Note that {@code @Where} restrictions are always applied and cannot be
|
||||
* disabled. Nor may they be parameterized. They're therefore <em>much</em>
|
||||
|
@ -75,21 +71,4 @@ public @interface Where {
|
|||
* A predicate, written in native SQL.
|
||||
*/
|
||||
String clause();
|
||||
|
||||
/**
|
||||
* If this restriction applies to an entity type, should it also be
|
||||
* applied when fetching a {@link jakarta.persistence.OneToMany} or
|
||||
* {@link jakarta.persistence.ManyToOne} association that targets
|
||||
* the entity type?
|
||||
* <p>
|
||||
* By default, the restriction is not applied unless the property
|
||||
* {@value org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS}
|
||||
* is explicitly disabled.
|
||||
*
|
||||
* @return {@code true} if the restriction should be applied even
|
||||
* if the configuration property is not enabled
|
||||
*
|
||||
* @since 6.2
|
||||
*/
|
||||
boolean applyInToManyFetch() default false;
|
||||
}
|
||||
|
|
|
@ -77,13 +77,13 @@ import org.hibernate.annotations.common.reflection.XProperty;
|
|||
import org.hibernate.boot.BootLogging;
|
||||
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
|
||||
import org.hibernate.boot.model.TypeDefinition;
|
||||
import org.hibernate.boot.model.source.internal.hbm.ModelBinder;
|
||||
import org.hibernate.boot.spi.AccessType;
|
||||
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
||||
import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrationDescriptor;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.PropertyData;
|
||||
import org.hibernate.boot.spi.SecondPass;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.spi.FilterDefinition;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
@ -165,7 +165,6 @@ import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
|
|||
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
|
||||
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
|
||||
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
|
||||
import static org.hibernate.cfg.AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS;
|
||||
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
||||
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
|
||||
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
||||
|
@ -1789,8 +1788,7 @@ public abstract class CollectionBinder {
|
|||
private String getWhereOnClassClause() {
|
||||
if ( property.getElementClass() != null ) {
|
||||
final Where whereOnClass = getOverridableAnnotation( property.getElementClass(), Where.class, getBuildingContext() );
|
||||
return whereOnClass != null
|
||||
&& ( whereOnClass.applyInToManyFetch() || useEntityWhereClauseForCollections() )
|
||||
return whereOnClass != null && ModelBinder.useEntityWhereClauseForCollections( buildingContext )
|
||||
? whereOnClass.clause()
|
||||
: null;
|
||||
}
|
||||
|
@ -1799,18 +1797,6 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean useEntityWhereClauseForCollections() {
|
||||
return getBoolean(
|
||||
USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS,
|
||||
buildingContext
|
||||
.getBuildingOptions()
|
||||
.getServiceRegistry()
|
||||
.getService( ConfigurationService.class )
|
||||
.getSettings(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private void addFilter(boolean hasAssociationTable, FilterJoinTable filter) {
|
||||
if ( hasAssociationTable ) {
|
||||
collection.addFilter(
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Properties;
|
|||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.FetchMode;
|
||||
import org.hibernate.Remove;
|
||||
import org.hibernate.annotations.SourceType;
|
||||
import org.hibernate.boot.MappingException;
|
||||
import org.hibernate.boot.jaxb.Origin;
|
||||
|
@ -160,6 +161,7 @@ import org.hibernate.usertype.CompositeUserType;
|
|||
import org.hibernate.usertype.ParameterizedType;
|
||||
import org.hibernate.usertype.UserType;
|
||||
|
||||
import static org.hibernate.cfg.AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS;
|
||||
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
|
||||
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
|
||||
|
||||
|
@ -196,6 +198,28 @@ public class ModelBinder {
|
|||
this.relationalObjectBinder = new RelationalObjectBinder( context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Interprets the setting {@value AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS},
|
||||
* which itself is deprecated
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Remove
|
||||
@Deprecated( since = "6.2" )
|
||||
public static boolean useEntityWhereClauseForCollections(MetadataBuildingContext buildingContext) {
|
||||
final Object explicitSetting = buildingContext
|
||||
.getBuildingOptions()
|
||||
.getServiceRegistry()
|
||||
.getService( ConfigurationService.class )
|
||||
.getSettings()
|
||||
.get( USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS );
|
||||
if ( explicitSetting != null ) {
|
||||
DeprecationLogger.DEPRECATION_LOGGER.deprecatedSettingNoReplacement( USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS );
|
||||
return ConfigurationHelper.toBoolean( explicitSetting, true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void bindEntityHierarchy(EntityHierarchySourceImpl hierarchySource) {
|
||||
final RootClass rootEntityDescriptor = new RootClass( hierarchySource.getRootEntityMappingDocument() );
|
||||
bindRootEntity( hierarchySource, rootEntityDescriptor );
|
||||
|
@ -3420,7 +3444,7 @@ public class ModelBinder {
|
|||
|
||||
final PersistentClass referencedEntityBinding = getReferencedEntityBinding( elementSource.getReferencedEntityName() );
|
||||
|
||||
if ( useEntityWhereClauseForCollections() ) {
|
||||
if ( useEntityWhereClauseForCollections( metadataBuildingContext ) ) {
|
||||
// For a one-to-many association, there are 2 possible sources of "where" clauses that apply
|
||||
// to the associated entity table:
|
||||
// 1) from the associated entity mapping; i.e., <class name="..." ... where="..." .../>
|
||||
|
@ -3490,7 +3514,7 @@ public class ModelBinder {
|
|||
// This "where" clause comes from the collection mapping; e.g., <set name="..." ... where="..." .../>
|
||||
getCollectionBinding().setWhere( getPluralAttributeSource().getWhere() );
|
||||
|
||||
if ( useEntityWhereClauseForCollections() ) {
|
||||
if ( useEntityWhereClauseForCollections( metadataBuildingContext ) ) {
|
||||
// For a many-to-many association, there are 2 possible sources of "where" clauses that apply
|
||||
// to the associated entity table (not the join table):
|
||||
// 1) from the associated entity mapping; i.e., <class name="..." ... where="..." .../>
|
||||
|
@ -3608,18 +3632,6 @@ public class ModelBinder {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean useEntityWhereClauseForCollections() {
|
||||
return ConfigurationHelper.getBoolean(
|
||||
AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS,
|
||||
metadataBuildingContext
|
||||
.getBuildingOptions()
|
||||
.getServiceRegistry()
|
||||
.getService( ConfigurationService.class )
|
||||
.getSettings(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private class PluralAttributeListSecondPass extends AbstractPluralAttributeSecondPass {
|
||||
public PluralAttributeListSecondPass(
|
||||
MappingDocument sourceDocument,
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.function.Supplier;
|
|||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.Interceptor;
|
||||
import org.hibernate.Remove;
|
||||
import org.hibernate.SessionFactoryObserver;
|
||||
import org.hibernate.boot.registry.selector.spi.StrategySelector;
|
||||
import org.hibernate.cache.internal.NoCachingRegionFactory;
|
||||
|
@ -2059,25 +2060,19 @@ public interface AvailableSettings {
|
|||
* The {@link org.hibernate.annotations.Where} annotation specifies a restriction
|
||||
* on the table rows which are visible as entity class instances or collection
|
||||
* elements.
|
||||
* <p>
|
||||
* This setting controls whether the restriction is applied when loading a
|
||||
* {@link jakarta.persistence.OneToMany one-to-many} or
|
||||
* {@link jakarta.persistence.ManyToMany many-to-many} association whose target
|
||||
* type defines the restriction.
|
||||
* <p>
|
||||
* By default, the restriction is applied. When this setting is disabled, the
|
||||
* restriction is not applied.
|
||||
* <p>
|
||||
* The setting has no effect on a collection of {@link jakarta.persistence.Embeddable
|
||||
* embeddable} values containing a {@link jakarta.persistence.ManyToOne many-to-one}
|
||||
* association to the entity.
|
||||
* <p>
|
||||
* This behavior may now be controlled in a safer and more granular way using
|
||||
* {@link org.hibernate.annotations.Where#applyInToManyFetch}, and so the use
|
||||
* of this configuration property is no longer recommended.
|
||||
* <p/>
|
||||
* This setting controls whether the restriction applied to an entity should
|
||||
* be applied to association fetches (one-to-one, many-to-one, one-to-many and many-to-many)
|
||||
* targeting the entity.
|
||||
*
|
||||
* @see org.hibernate.annotations.Where#applyInToManyFetch
|
||||
* @apiNote The setting is very misnamed - it applies across all entity associations, not just collections.
|
||||
*
|
||||
* @implSpec Enabled ({@code true}) by default, meaning the restriction is applied. When this setting
|
||||
* is disabled ({@code false}), the restriction is not applied.
|
||||
*
|
||||
* @deprecated Originally added as a backwards compatibility flag
|
||||
*/
|
||||
@Remove @Deprecated( forRemoval = true, since = "6.2" )
|
||||
String USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = "hibernate.use_entity_where_clause_for_collections";
|
||||
|
||||
/**
|
||||
|
|
|
@ -266,4 +266,15 @@ public interface DeprecationLogger extends BasicLogger {
|
|||
)
|
||||
void deprecatedSettingForRemoval(String settingName, String defaultValue);
|
||||
|
||||
/**
|
||||
* Different from {@link #deprecatedSetting} in that sometimes there is no
|
||||
* direct alternative
|
||||
*/
|
||||
@LogMessage(level = WARN)
|
||||
@Message(
|
||||
id = 90000030,
|
||||
value = "The [%s] configuration is deprecated and will be removed."
|
||||
)
|
||||
void deprecatedSettingNoReplacement(String settingName);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.HibernateException;
|
|||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.loader.ast.spi.NaturalIdLoadOptions;
|
||||
|
@ -155,6 +156,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
|
|||
lockOptions,
|
||||
fetchProcessor,
|
||||
true,
|
||||
LoadQueryInfluencers.NONE,
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
|||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
|
@ -75,6 +76,7 @@ class DatabaseSnapshotExecutor {
|
|||
LockOptions.NONE,
|
||||
(fetchParent, creationState) -> ImmutableFetchList.EMPTY,
|
||||
true,
|
||||
LoadQueryInfluencers.NONE,
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
|
|
|
@ -380,6 +380,7 @@ public class LoaderSelectBuilder {
|
|||
lockOptions,
|
||||
this::visitFetches,
|
||||
forceIdentifierSelection,
|
||||
loadQueryInfluencers,
|
||||
creationContext
|
||||
);
|
||||
|
||||
|
@ -962,6 +963,7 @@ public class LoaderSelectBuilder {
|
|||
lockOptions,
|
||||
this::visitFetches,
|
||||
numberOfKeysToLoad > 1,
|
||||
loadQueryInfluencers,
|
||||
creationContext
|
||||
);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import jakarta.persistence.CacheStoreMode;
|
|||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.graph.spi.AppliedGraph;
|
||||
import org.hibernate.metamodel.mapping.AssociationKey;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
|
@ -51,6 +52,7 @@ public class LoaderSqlAstCreationState
|
|||
|
||||
private final SqlAliasBaseManager sqlAliasBaseManager;
|
||||
private final boolean forceIdentifierSelection;
|
||||
private final LoadQueryInfluencers loadQueryInfluencers;
|
||||
private final SqlAstCreationContext sf;
|
||||
private final SqlAstQueryPartProcessingStateImpl processingState;
|
||||
private final FromClauseAccess fromClauseAccess;
|
||||
|
@ -68,12 +70,14 @@ public class LoaderSqlAstCreationState
|
|||
LockOptions lockOptions,
|
||||
FetchProcessor fetchProcessor,
|
||||
boolean forceIdentifierSelection,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
SqlAstCreationContext sf) {
|
||||
this.sqlAliasBaseManager = sqlAliasBaseManager;
|
||||
this.fromClauseAccess = fromClauseAccess;
|
||||
this.lockOptions = lockOptions;
|
||||
this.fetchProcessor = fetchProcessor;
|
||||
this.forceIdentifierSelection = forceIdentifierSelection;
|
||||
this.loadQueryInfluencers = loadQueryInfluencers;
|
||||
this.sf = sf;
|
||||
this.processingState = new SqlAstQueryPartProcessingStateImpl(
|
||||
queryPart,
|
||||
|
@ -114,6 +118,11 @@ public class LoaderSqlAstCreationState
|
|||
return sqlAliasBaseManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadQueryInfluencers getLoadQueryInfluencers() {
|
||||
return loadQueryInfluencers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLockMode(String identificationVariable, LockMode explicitLockMode) {
|
||||
throw new UnsupportedOperationException( "Registering lock modes should only be done for result set mappings" );
|
||||
|
|
|
@ -61,6 +61,7 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
|||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingModelCreationLogger;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
|
@ -1596,6 +1597,15 @@ public class MappingModelCreationHelper {
|
|||
|| value instanceof OneToOne && value.isNullable()
|
||||
|| value instanceof ManyToOne && value.isNullable() && ( (ManyToOne) value ).isIgnoreNotFound() ) {
|
||||
fetchTiming = FetchTiming.IMMEDIATE;
|
||||
if ( lazy ) {
|
||||
if ( MappingModelCreationLogger.DEBUG_ENABLED ) {
|
||||
MappingModelCreationLogger.LOGGER.debugf(
|
||||
"Forcing FetchTiming.IMMEDIATE for to-one association : %s.%s",
|
||||
declaringType.getNavigableRole(),
|
||||
bootProperty.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, role, sessionFactory );
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -48,7 +47,6 @@ import org.hibernate.sql.ast.spi.FromClauseAccess;
|
|||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasStemHelper;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.CollectionTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
|
@ -58,6 +56,7 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
|||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.PredicateCollector;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
|
@ -570,6 +569,45 @@ public class PluralAttributeMappingImpl
|
|||
boolean fetched,
|
||||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final PredicateCollector predicateCollector = new PredicateCollector();
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
explicitSourceAlias,
|
||||
explicitSqlAliasBase,
|
||||
requestedJoinType,
|
||||
fetched,
|
||||
predicateCollector::applyPredicate,
|
||||
creationState
|
||||
);
|
||||
|
||||
getCollectionDescriptor().applyBaseRestrictions(
|
||||
predicateCollector::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
creationState.getLoadQueryInfluencers().getEnabledFilters(),
|
||||
null,
|
||||
creationState
|
||||
);
|
||||
|
||||
getCollectionDescriptor().applyBaseManyToManyRestrictions(
|
||||
predicateCollector::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
creationState.getLoadQueryInfluencers().getEnabledFilters(),
|
||||
null,
|
||||
creationState
|
||||
);
|
||||
|
||||
return new TableGroupJoin(
|
||||
navigablePath,
|
||||
determineSqlJoinType( lhs, requestedJoinType, fetched ),
|
||||
tableGroup,
|
||||
predicateCollector.getPredicate()
|
||||
);
|
||||
}
|
||||
|
||||
private SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
final SqlAstJoinType joinType;
|
||||
if ( requestedJoinType == null ) {
|
||||
if ( fetched ) {
|
||||
|
@ -582,25 +620,7 @@ public class PluralAttributeMappingImpl
|
|||
else {
|
||||
joinType = requestedJoinType;
|
||||
}
|
||||
final java.util.List<Predicate> predicates = new ArrayList<>( 2 );
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
explicitSourceAlias,
|
||||
explicitSqlAliasBase,
|
||||
requestedJoinType,
|
||||
fetched,
|
||||
predicates::add,
|
||||
creationState
|
||||
);
|
||||
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
|
||||
navigablePath,
|
||||
joinType,
|
||||
tableGroup,
|
||||
null
|
||||
);
|
||||
predicates.forEach( tableGroupJoin::applyPredicate );
|
||||
return tableGroupJoin;
|
||||
return joinType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -644,6 +664,7 @@ public class PluralAttributeMappingImpl
|
|||
if ( predicateConsumer != null ) {
|
||||
predicateConsumer.accept( getKeyDescriptor().generateJoinPredicate( lhs, tableGroup, creationState ) );
|
||||
}
|
||||
|
||||
return tableGroup;
|
||||
}
|
||||
|
||||
|
|
|
@ -1672,20 +1672,8 @@ public class ToOneAttributeMapping
|
|||
// This is vital for the map key property check that comes next
|
||||
assert !( lhs instanceof PluralTableGroup );
|
||||
|
||||
final SqlAstJoinType joinType;
|
||||
if ( requestedJoinType == null ) {
|
||||
if ( fetched ) {
|
||||
joinType = getDefaultSqlAstJoinType( lhs );
|
||||
}
|
||||
else {
|
||||
joinType = SqlAstJoinType.INNER;
|
||||
}
|
||||
}
|
||||
else {
|
||||
joinType = requestedJoinType;
|
||||
}
|
||||
|
||||
final FromClauseAccess fromClauseAccess = creationState.getFromClauseAccess();
|
||||
final SqlAstJoinType joinType = determineSqlJoinType( lhs, requestedJoinType, fetched );
|
||||
|
||||
// If a parent is a collection part, there is no custom predicate and the join is INNER or LEFT
|
||||
// we check if this attribute is the map key property to reuse the existing index table group
|
||||
|
@ -1805,20 +1793,35 @@ public class ToOneAttributeMapping
|
|||
creationState
|
||||
) );
|
||||
|
||||
if ( hasNotFoundAction() ) {
|
||||
getAssociatedEntityMappingType().applyWhereRestrictions(
|
||||
join::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
null
|
||||
);
|
||||
}
|
||||
// Note specifically we DO NOT apply `@Filter` restrictions
|
||||
getAssociatedEntityMappingType().applyWhereRestrictions(
|
||||
join::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
creationState
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return join;
|
||||
}
|
||||
|
||||
private SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||
final SqlAstJoinType joinType;
|
||||
if ( requestedJoinType == null ) {
|
||||
if ( fetched ) {
|
||||
joinType = getDefaultSqlAstJoinType( lhs );
|
||||
}
|
||||
else {
|
||||
joinType = SqlAstJoinType.INNER;
|
||||
}
|
||||
}
|
||||
else {
|
||||
joinType = requestedJoinType;
|
||||
}
|
||||
return joinType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyTableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
|
|
|
@ -928,6 +928,7 @@ public abstract class AbstractCollectionPersister
|
|||
LockOptions.NONE,
|
||||
(fetchParent, creationState) -> ImmutableFetchList.EMPTY,
|
||||
true,
|
||||
LoadQueryInfluencers.NONE,
|
||||
getFactory()
|
||||
);
|
||||
|
||||
|
|
|
@ -287,15 +287,6 @@ public class OneToManyPersister extends AbstractCollectionPersister {
|
|||
return ( (Joinable) getElementPersister() ).getTableName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyWhereRestrictions(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
TableGroup tableGroup,
|
||||
boolean useQualifier,
|
||||
SqlAstCreationState creationState) {
|
||||
super.applyWhereRestrictions( predicateConsumer, tableGroup, useQualifier, creationState );
|
||||
}
|
||||
|
||||
protected void applyWhereFragments(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
String alias,
|
||||
|
|
|
@ -1680,6 +1680,7 @@ public abstract class AbstractEntityPersister
|
|||
LockOptions.NONE,
|
||||
this::fetchProcessor,
|
||||
true,
|
||||
LoadQueryInfluencers.NONE,
|
||||
getFactory()
|
||||
);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.function.Function;
|
|||
import org.hibernate.Internal;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.internal.util.collections.StandardStack;
|
||||
|
@ -72,6 +73,7 @@ public class DomainResultCreationStateImpl
|
|||
|
||||
private final JdbcValuesMetadata jdbcResultsMetadata;
|
||||
private final Consumer<SqlSelection> sqlSelectionConsumer;
|
||||
private final LoadQueryInfluencers loadQueryInfluencers;
|
||||
private final Map<ColumnReferenceKey, ResultSetMappingSqlSelection> sqlSelectionMap = new HashMap<>();
|
||||
private boolean allowPositionalSelections = true;
|
||||
|
||||
|
@ -92,10 +94,12 @@ public class DomainResultCreationStateImpl
|
|||
JdbcValuesMetadata jdbcResultsMetadata,
|
||||
Map<String, Map<String, DynamicFetchBuilderLegacy>> legacyFetchBuilders,
|
||||
Consumer<SqlSelection> sqlSelectionConsumer,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
this.stateIdentifier = stateIdentifier;
|
||||
this.jdbcResultsMetadata = jdbcResultsMetadata;
|
||||
this.sqlSelectionConsumer = sqlSelectionConsumer;
|
||||
this.loadQueryInfluencers = loadQueryInfluencers;
|
||||
this.fromClauseAccess = new FromClauseAccessImpl();
|
||||
this.sqlAliasBaseManager = new SqlAliasBaseManager();
|
||||
|
||||
|
@ -245,6 +249,11 @@ public class DomainResultCreationStateImpl
|
|||
return sqlAliasBaseManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadQueryInfluencers getLoadQueryInfluencers() {
|
||||
return loadQueryInfluencers;
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SqlAstProcessingState
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.util.function.Consumer;
|
|||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException;
|
||||
|
@ -185,6 +186,7 @@ public class ResultSetMappingImpl implements ResultSetMapping {
|
|||
@Override
|
||||
public JdbcValuesMapping resolve(
|
||||
JdbcValuesMetadata jdbcResultsMetadata,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
|
||||
final int numberOfResults;
|
||||
|
@ -205,6 +207,7 @@ public class ResultSetMappingImpl implements ResultSetMapping {
|
|||
jdbcResultsMetadata,
|
||||
legacyFetchBuilders,
|
||||
sqlSelections::add,
|
||||
loadQueryInfluencers,
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
|||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.Restrictable;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.metamodel.mapping.SelectableMappings;
|
||||
import org.hibernate.metamodel.mapping.SqlExpressible;
|
||||
|
@ -394,7 +395,6 @@ import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
|||
import org.hibernate.type.descriptor.java.EnumJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeHelper;
|
||||
import org.hibernate.type.descriptor.java.TemporalJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -3052,15 +3052,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
);
|
||||
|
||||
joinedTableGroup = joinedTableGroupJoin.getJoinedGroup();
|
||||
|
||||
pluralAttributeMapping.applyBaseRestrictions(
|
||||
(predicate) -> addCollectionFilterPredicate( joinedTableGroup.getNavigablePath(), predicate ),
|
||||
joinedTableGroup,
|
||||
true,
|
||||
getLoadQueryInfluencers().getEnabledFilters(),
|
||||
null,
|
||||
this
|
||||
);
|
||||
//
|
||||
// pluralAttributeMapping.applyBaseRestrictions(
|
||||
// (predicate) -> addCollectionFilterPredicate( joinedTableGroup.getNavigablePath(), predicate ),
|
||||
// joinedTableGroup,
|
||||
// true,
|
||||
// getLoadQueryInfluencers().getEnabledFilters(),
|
||||
// null,
|
||||
// this
|
||||
// );
|
||||
}
|
||||
else {
|
||||
assert modelPart instanceof TableGroupJoinProducer;
|
||||
|
@ -7145,13 +7145,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming();
|
||||
boolean joined = false;
|
||||
|
||||
EntityGraphTraversalState.TraversalResult traversalResult = null;
|
||||
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
|
||||
final SqmAttributeJoin<?, ?> fetchedJoin = fromClauseIndex.findFetchedJoinByPath( resolvedNavigablePath );
|
||||
boolean explicitFetch = false;
|
||||
|
||||
final NavigablePath fetchablePath;
|
||||
final Integer maxDepth = getCreationContext().getMaximumFetchDepth();
|
||||
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
|
||||
final SqmAttributeJoin<?, ?> fetchedJoin = fromClauseIndex.findFetchedJoinByPath( resolvedNavigablePath );
|
||||
|
||||
boolean explicitFetch = false;
|
||||
EntityGraphTraversalState.TraversalResult traversalResult = null;
|
||||
|
||||
if ( fetchedJoin != null ) {
|
||||
fetchablePath = fetchedJoin.getNavigablePath();
|
||||
// there was an explicit fetch in the SQM
|
||||
|
@ -7224,17 +7225,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
}
|
||||
}
|
||||
|
||||
// final TableGroup existingJoinedGroup = fromClauseIndex.findTableGroup( fetchablePath );
|
||||
// if ( existingJoinedGroup != null ) {
|
||||
// we can use this to trigger the fetch from the joined group.
|
||||
|
||||
// todo (6.0) : do we want to do this though?
|
||||
// On the positive side it would allow EntityGraph to use the existing TableGroup. But that ties in
|
||||
// to the discussion above regarding how to handle eager and EntityGraph (JOIN versus SELECT).
|
||||
// Can be problematic if the existing one is restricted
|
||||
// fetchTiming = FetchTiming.IMMEDIATE;
|
||||
// }
|
||||
|
||||
// lastly, account for any app-defined max-fetch-depth
|
||||
if ( maxDepth != null ) {
|
||||
if ( fetchDepth >= maxDepth ) {
|
||||
|
@ -7259,6 +7249,29 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
BaseSqmToSqlAstConverter.this
|
||||
);
|
||||
lhs.addTableGroupJoin( tableGroupJoin );
|
||||
|
||||
// if ( fetchable instanceof PluralAttributeMapping ) {
|
||||
// // apply restrictions
|
||||
// ( (Restrictable) fetchable ).applyBaseRestrictions(
|
||||
// tableGroupJoin::applyPredicate,
|
||||
// tableGroupJoin.getJoinedGroup(),
|
||||
// true,
|
||||
// loadQueryInfluencers.getEnabledFilters(),
|
||||
// null,
|
||||
// getSqlAstCreationState()
|
||||
// );
|
||||
//
|
||||
// ( (PluralAttributeMapping) fetchable ).applyBaseManyToManyRestrictions(
|
||||
// tableGroupJoin::applyPredicate,
|
||||
// tableGroupJoin.getJoinedGroup(),
|
||||
// true,
|
||||
// loadQueryInfluencers.getEnabledFilters(),
|
||||
// null,
|
||||
// getSqlAstCreationState()
|
||||
// );
|
||||
// }
|
||||
|
||||
// and return the joined group
|
||||
return tableGroupJoin.getJoinedGroup();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.List;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
|
||||
|
@ -62,6 +63,11 @@ public class FakeSqmToSqlAstConverter extends BaseSemanticQueryWalker implements
|
|||
return creationState.getSqlAliasBaseGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadQueryInfluencers getLoadQueryInfluencers() {
|
||||
return LoadQueryInfluencers.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLockMode(String identificationVariable, LockMode explicitLockMode) {
|
||||
creationState.registerLockMode( identificationVariable, explicitLockMode );
|
||||
|
|
|
@ -177,7 +177,7 @@ public class OutputsImpl implements Outputs {
|
|||
null,
|
||||
null,
|
||||
this.context.getQueryOptions(),
|
||||
resultSetMapping.resolve( resultSetAccess, getSessionFactory() ),
|
||||
resultSetMapping.resolve( resultSetAccess, context.getSession().getLoadQueryInfluencers(), getSessionFactory() ),
|
||||
null,
|
||||
executionContext
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.sql.ast.spi;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
|
||||
/**
|
||||
* Access to stuff used while creating a SQL AST
|
||||
|
@ -24,5 +25,7 @@ public interface SqlAstCreationState {
|
|||
|
||||
SqlAliasBaseGenerator getSqlAliasBaseGenerator();
|
||||
|
||||
LoadQueryInfluencers getLoadQueryInfluencers();
|
||||
|
||||
void registerLockMode(String identificationVariable, LockMode explicitLockMode);
|
||||
}
|
||||
|
|
|
@ -475,13 +475,13 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
final JdbcValuesMetadata metadataForCache;
|
||||
final JdbcValuesMapping jdbcValuesMapping;
|
||||
if ( queryResultsCacheKey == null ) {
|
||||
jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, factory );
|
||||
jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, session.getLoadQueryInfluencers(), factory );
|
||||
metadataForCache = null;
|
||||
}
|
||||
else {
|
||||
// If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata
|
||||
final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess );
|
||||
jdbcValuesMapping = mappingProducer.resolve( capturingMetadata, factory );
|
||||
jdbcValuesMapping = mappingProducer.resolve( capturingMetadata, session.getLoadQueryInfluencers(), factory );
|
||||
metadataForCache = capturingMetadata.resolveMetadataForCache();
|
||||
}
|
||||
|
||||
|
@ -498,10 +498,10 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
else {
|
||||
final JdbcValuesMapping jdbcValuesMapping;
|
||||
if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) {
|
||||
jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, factory );
|
||||
jdbcValuesMapping = mappingProducer.resolve( resultSetAccess, session.getLoadQueryInfluencers(), factory );
|
||||
}
|
||||
else {
|
||||
jdbcValuesMapping = mappingProducer.resolve( (JdbcValuesMetadata) cachedResults.get( 0 ), factory );
|
||||
jdbcValuesMapping = mappingProducer.resolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory );
|
||||
}
|
||||
return new JdbcValuesCacheHit( cachedResults, jdbcValuesMapping );
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.query.NativeQuery;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
|
@ -43,6 +44,7 @@ public class JdbcValuesMappingProducerStandard implements JdbcValuesMappingProdu
|
|||
@Override
|
||||
public JdbcValuesMapping resolve(
|
||||
JdbcValuesMetadata jdbcResultsMetadata,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
final List<SqlSelection> sqlSelections = resolvedMapping.getSqlSelections();
|
||||
List<SqlSelection> resolvedSelections = null;
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.sql.results.jdbc.spi;
|
|||
import java.util.Set;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.query.results.ResultSetMapping;
|
||||
|
||||
|
@ -32,6 +33,7 @@ public interface JdbcValuesMappingProducer {
|
|||
*/
|
||||
JdbcValuesMapping resolve(
|
||||
JdbcValuesMetadata jdbcResultsMetadata,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
SessionFactoryImplementor sessionFactory);
|
||||
|
||||
void addAffectedTableNames(Set<String> affectedTableNames, SessionFactoryImplementor sessionFactory);
|
||||
|
|
|
@ -64,7 +64,7 @@ public class LoadPlanBuilderTest {
|
|||
|
||||
final List<DomainResult<?>> domainResults = loadPlan.getJdbcSelect()
|
||||
.getJdbcValuesMappingProducer()
|
||||
.resolve( null, sessionFactory )
|
||||
.resolve( null, LoadQueryInfluencers.NONE, sessionFactory )
|
||||
.getDomainResults();
|
||||
|
||||
assertThat( domainResults ).hasSize( 1 );
|
||||
|
@ -105,7 +105,7 @@ public class LoadPlanBuilderTest {
|
|||
);
|
||||
final List<DomainResult<?>> domainResults = loadPlan.getJdbcSelect()
|
||||
.getJdbcValuesMappingProducer()
|
||||
.resolve( null, sessionFactory )
|
||||
.resolve( null, LoadQueryInfluencers.NONE, sessionFactory )
|
||||
.getDomainResults();
|
||||
|
||||
assertThat( domainResults ).hasSize( 1 );
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.mapping.where;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.NamedAttributeNode;
|
||||
import jakarta.persistence.NamedEntityGraph;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static java.text.MessageFormat.format;
|
||||
import static java.util.Objects.hash;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
@NamedEntityGraph(
|
||||
name = "user-entity-graph",
|
||||
attributeNodes = {
|
||||
@NamedAttributeNode(value = "detail"),
|
||||
@NamedAttributeNode(value = "skills")
|
||||
}
|
||||
)
|
||||
@Table(name = "t_users")
|
||||
@Entity(name = "User")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@Column(name = "user_id")
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "user_name")
|
||||
private String name;
|
||||
|
||||
@OneToOne(mappedBy = "user", fetch = LAZY)
|
||||
private UserDetail detail;
|
||||
|
||||
@OneToMany(mappedBy = "user", fetch = LAZY)
|
||||
private Set<UserSkill> skills = new HashSet<>();
|
||||
|
||||
protected User() {
|
||||
}
|
||||
|
||||
public User(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public User(String name, UserDetail detail) {
|
||||
this.name = name;
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UserDetail getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(UserDetail detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public Set<UserSkill> getSkills() {
|
||||
return skills;
|
||||
}
|
||||
|
||||
public void setSkills(Set<UserSkill> skills) {
|
||||
this.skills = skills;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
User that = (User) obj;
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash(id);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return format("User(id={0}, name={1}, detail={2}, skills=[{3}])",
|
||||
// id, name, detail, skills.stream().map(UserSkill::toString).collect(joining(", ")));
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.where;
|
||||
|
||||
import org.hibernate.annotations.Where;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static java.text.MessageFormat.format;
|
||||
import static java.util.Objects.hash;
|
||||
|
||||
@Where(clause = "is_active = true")
|
||||
@Table(name = "t_user_details")
|
||||
@Entity(name = "UserDetail")
|
||||
public class UserDetail {
|
||||
|
||||
@Id
|
||||
@Column(name = "detail_id")
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "city")
|
||||
private String city;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean active;
|
||||
|
||||
@OneToOne(fetch = LAZY)
|
||||
@JoinColumn(name = "user_fk")
|
||||
private User user;
|
||||
|
||||
protected UserDetail() {
|
||||
}
|
||||
|
||||
public UserDetail(Integer id, String city, Boolean active, User user) {
|
||||
this.id = id;
|
||||
this.city = city;
|
||||
this.active = active;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(Boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
UserDetail that = (UserDetail) obj;
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format("UserDetail(id={0}, city={1}, active={2})", id, city, active);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.where;
|
||||
|
||||
import org.hibernate.annotations.Where;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static java.text.MessageFormat.format;
|
||||
import static java.util.Objects.hash;
|
||||
|
||||
@Where(clause = "has_deleted = false")
|
||||
@Table(name = "t_user_skills")
|
||||
@Entity(name = "UserSkill")
|
||||
public class UserSkill {
|
||||
|
||||
@Id
|
||||
@Column(name = "skill_id")
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "skill_name")
|
||||
private String skillName;
|
||||
|
||||
@Column(name = "has_deleted")
|
||||
private Boolean deleted;
|
||||
|
||||
@ManyToOne(fetch = LAZY)
|
||||
@JoinColumn(name = "user_fk")
|
||||
private User user;
|
||||
|
||||
protected UserSkill() {
|
||||
}
|
||||
|
||||
public UserSkill(Integer id, String skillName, Boolean deleted, User user) {
|
||||
this.id = id;
|
||||
this.skillName = skillName;
|
||||
this.deleted = deleted;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSkillName() {
|
||||
return skillName;
|
||||
}
|
||||
|
||||
public void setSkillName(String skillName) {
|
||||
this.skillName = skillName;
|
||||
}
|
||||
|
||||
public Boolean getDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(Boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
UserSkill that = (UserSkill) obj;
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format("UserSkill(id={0}, skillName={1}, deleted={2})", id, skillName, deleted);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* 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.mapping.where;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.Where;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.jpa.SpecHints;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.internal.util.collections.CollectionHelper.toSettingsMap;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link Where} handling.
|
||||
*
|
||||
* @implNote Requires H2 simply because we need hard-coded schema export. The schema is simple and would
|
||||
* probably work on a larger number of databases; but there should really be nothing database specific in
|
||||
* these tests.
|
||||
*/
|
||||
@ServiceRegistry(
|
||||
settings = {
|
||||
@Setting(
|
||||
name = AvailableSettings.JAKARTA_HBM2DDL_CREATE_SCRIPT_SOURCE,
|
||||
value = "org/hibernate/orm/test/mapping/schema.sql"
|
||||
),
|
||||
@Setting(
|
||||
name = AvailableSettings.JAKARTA_HBM2DDL_CREATE_SOURCE,
|
||||
value = "script"
|
||||
),
|
||||
@Setting(
|
||||
name = AvailableSettings.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR,
|
||||
value = "org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor"
|
||||
),
|
||||
}
|
||||
)
|
||||
@DomainModel( annotatedClasses = { User.class, UserDetail.class, UserSkill.class } )
|
||||
@SessionFactory( useCollectingStatementInspector = true )
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-16019" )
|
||||
@RequiresDialect( H2Dialect.class )
|
||||
public class WhereFragmentTests {
|
||||
/**
|
||||
* Loads a User, fetching their detail and skills using an entity-graph
|
||||
*/
|
||||
public User findUserByIdUsingEntityGraph(Integer id, SessionFactoryScope factoryScope) {
|
||||
return factoryScope.fromTransaction( (session) -> {
|
||||
final Map<String, Object> properties = toSettingsMap(
|
||||
SpecHints.HINT_SPEC_FETCH_GRAPH,
|
||||
session.getEntityGraph("user-entity-graph")
|
||||
);
|
||||
|
||||
return session.find(User.class, id, properties);
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a User (via HQL), fetching their detail and skills using an entity-graph
|
||||
*/
|
||||
public User findUserByNameUsingEntityGraph(String name, SessionFactoryScope factoryScope) {
|
||||
return factoryScope.fromTransaction( (session) -> {
|
||||
final RootGraphImplementor<?> entityGraph = session.getEntityGraph( "user-entity-graph" );
|
||||
return session.createSelectionQuery( "from User where name = :name", User.class )
|
||||
.setParameter( "name", name )
|
||||
.setHint( SpecHints.HINT_SPEC_FETCH_GRAPH, entityGraph )
|
||||
.uniqueResult();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a User (via HQL), fetching their detail and skills using HQL fetch joins
|
||||
*/
|
||||
public User findUserByNameUsingHqlFetches(String name, SessionFactoryScope factoryScope) {
|
||||
final String queryString = "from User u left join fetch u.detail left join fetch u.skills where u.name=?1";
|
||||
return factoryScope.fromTransaction( (session) ->session
|
||||
.createSelectionQuery( queryString, User.class )
|
||||
.setParameter( 1, name )
|
||||
.uniqueResult()
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createNativeMutationQuery( "delete from t_user_skills" ).executeUpdate();
|
||||
session.createNativeMutationQuery( "delete t_user_details" ).executeUpdate();
|
||||
session.createNativeMutationQuery( "delete t_users" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Alice - no detail and no skills
|
||||
|
||||
/**
|
||||
* Create the user Alice who has no details and no skills
|
||||
*/
|
||||
private void createAlice(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
User user = new User( 1, "Alice");
|
||||
session.persist( user );
|
||||
} );
|
||||
}
|
||||
|
||||
private void verifyAlice(User alice) {
|
||||
assertThat( alice ).isNotNull();
|
||||
assertThat( alice.getName() ).isEqualTo( "Alice" );
|
||||
assertThat( alice.getDetail() ).isNull();
|
||||
assertThat( alice.getSkills() ).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAliceById(SessionFactoryScope scope) {
|
||||
createAlice( scope );
|
||||
|
||||
User alice = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyAlice( alice );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAliceByHql(SessionFactoryScope scope) {
|
||||
createAlice( scope );
|
||||
|
||||
User alice = findUserByNameUsingEntityGraph("Alice", scope);
|
||||
verifyAlice( alice );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAliceByHqlWithFetchJoin(SessionFactoryScope scope) {
|
||||
createAlice( scope );
|
||||
|
||||
User alice = findUserByNameUsingHqlFetches("Alice", scope);
|
||||
verifyAlice( alice );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Bob - an inactive detail and no skills
|
||||
|
||||
/**
|
||||
* Create the user Bob who has an inactive detail and no skills
|
||||
*/
|
||||
private static void createBob(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
User user = new User( 1, "Bob" );
|
||||
session.persist( user );
|
||||
|
||||
UserDetail detail = new UserDetail( 1, "New York", false, user );
|
||||
session.persist( detail );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void verifyBob(User bob) {
|
||||
assertThat( bob ).isNotNull();
|
||||
assertThat( bob.getName() ).isEqualTo( "Bob" );
|
||||
assertThat( bob.getDetail() ).isNull();
|
||||
assertThat( bob.getSkills() ).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindBobById(SessionFactoryScope scope) {
|
||||
createBob( scope );
|
||||
|
||||
User bob = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyBob( bob );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindBobByHql(SessionFactoryScope scope) {
|
||||
createBob( scope );
|
||||
|
||||
User bob = findUserByNameUsingEntityGraph( "Bob", scope );
|
||||
verifyBob( bob );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindBobByHqlWithFetchJoin(SessionFactoryScope scope) {
|
||||
createBob( scope );
|
||||
|
||||
User bob = findUserByNameUsingHqlFetches( "Bob", scope );
|
||||
verifyBob( bob );
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Charlie - an active detail and a deleted skill
|
||||
|
||||
/**
|
||||
* Create the user Bob who has an active detail and a deleted skill
|
||||
*/
|
||||
private static void createCharlie(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
User user = new User( 1, "Charlie" );
|
||||
session.persist(user);
|
||||
|
||||
UserDetail detail = new UserDetail( 1, "Paris", true, user );
|
||||
session.persist(detail);
|
||||
|
||||
UserSkill skill = new UserSkill( 1, "Java", true, user );
|
||||
session.persist(skill);
|
||||
} );
|
||||
}
|
||||
|
||||
private void verifyCharlie(User user) {
|
||||
assertThat( user ).isNotNull();
|
||||
assertThat( user.getName() ).isEqualTo( "Charlie" );
|
||||
assertThat( user.getDetail() ).isNotNull();
|
||||
assertThat( user.getDetail().getActive() ).isTrue();
|
||||
assertThat( user.getDetail().getCity() ).isEqualTo( "Paris" );
|
||||
assertThat( user.getSkills() ).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCharlieById(SessionFactoryScope scope) {
|
||||
createCharlie( scope );
|
||||
verifyCharlie( findUserByIdUsingEntityGraph( 1, scope ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCharlieByHqlWithGraph(SessionFactoryScope scope) {
|
||||
createCharlie( scope );
|
||||
verifyCharlie( findUserByNameUsingEntityGraph( "Charlie", scope ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCharlieByHqlWithJoinFetch(SessionFactoryScope scope) {
|
||||
createCharlie( scope );
|
||||
verifyCharlie( findUserByNameUsingHqlFetches( "Charlie", scope ) );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// David - multiple details, single skill
|
||||
|
||||
/**
|
||||
* Create the user David with multiple details, single skill
|
||||
*/
|
||||
private static void createDavid(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (entityManager) -> {
|
||||
User user = new User( 1, "David" );
|
||||
entityManager.persist( user );
|
||||
|
||||
UserDetail detail1 = new UserDetail( 1, "London", false, user );
|
||||
entityManager.persist( detail1 );
|
||||
|
||||
UserDetail detail2 = new UserDetail( 2, "Rome", true, user );
|
||||
entityManager.persist( detail2 );
|
||||
|
||||
UserSkill skill = new UserSkill( 1, "Kotlin", false, user );
|
||||
entityManager.persist( skill );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void verifyDavid(User david) {
|
||||
assertNotNull( david );
|
||||
assertEquals("David", david.getName());
|
||||
|
||||
assertNotNull( david.getDetail());
|
||||
assertTrue( david.getDetail().getActive());
|
||||
assertEquals("Rome", david.getDetail().getCity());
|
||||
|
||||
assertFalse( david.getSkills().isEmpty());
|
||||
assertTrue( david.getSkills().stream().noneMatch(UserSkill::getDeleted));
|
||||
assertEquals(1, david.getSkills().size());
|
||||
assertEquals("Kotlin", david.getSkills().stream().findFirst().orElseThrow(IllegalStateException::new).getSkillName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindDavidById(SessionFactoryScope scope) {
|
||||
createDavid( scope );
|
||||
|
||||
User david = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyDavid( david );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindDavidByHqlWithGraph(SessionFactoryScope scope) {
|
||||
createDavid( scope );
|
||||
|
||||
User david = findUserByNameUsingEntityGraph("David", scope );
|
||||
verifyDavid( david );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindDavidByHqlWithJoinFetch(SessionFactoryScope scope) {
|
||||
createDavid( scope );
|
||||
|
||||
User david = findUserByNameUsingHqlFetches("David", scope );
|
||||
verifyDavid( david );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Eve
|
||||
// - 1 active and 2 inactive details
|
||||
// - 1 active and 1 inactive skills
|
||||
|
||||
private static void createEve(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (entityManager) -> {
|
||||
User user = new User( 1, "Eve" );
|
||||
entityManager.persist( user );
|
||||
|
||||
UserDetail detail1 = new UserDetail( 1, "Moscow", false, user );
|
||||
entityManager.persist( detail1 );
|
||||
|
||||
UserDetail detail2 = new UserDetail( 2, "Istanbul", false, user );
|
||||
entityManager.persist( detail2 );
|
||||
|
||||
UserDetail detail3 = new UserDetail( 3, "Berlin", true, user );
|
||||
entityManager.persist( detail3 );
|
||||
|
||||
UserSkill skill1 = new UserSkill( 1, "Python", true, user );
|
||||
entityManager.persist( skill1 );
|
||||
|
||||
UserSkill skill2 = new UserSkill( 2, "Ruby", false, user );
|
||||
entityManager.persist( skill2 );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void verifyEve(User eve) {
|
||||
assertNotNull( eve );
|
||||
assertEquals("Eve", eve.getName());
|
||||
|
||||
assertNotNull( eve.getDetail());
|
||||
assertTrue( eve.getDetail().getActive());
|
||||
assertEquals("Berlin", eve.getDetail().getCity());
|
||||
|
||||
assertFalse( eve.getSkills().isEmpty());
|
||||
assertTrue( eve.getSkills().stream().noneMatch(UserSkill::getDeleted));
|
||||
assertEquals(1, eve.getSkills().size());
|
||||
assertEquals("Ruby", eve.getSkills().stream().findFirst().orElseThrow(IllegalStateException::new).getSkillName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindEveById(SessionFactoryScope scope) {
|
||||
createEve( scope );
|
||||
|
||||
User eve = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyEve( eve );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindEveByHqlWithGraph(SessionFactoryScope scope) {
|
||||
createEve( scope );
|
||||
|
||||
User eve = findUserByNameUsingEntityGraph( "Eve", scope );
|
||||
verifyEve( eve );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindEveByHqlWithJoinFetch(SessionFactoryScope scope) {
|
||||
createEve( scope );
|
||||
|
||||
User eve = findUserByNameUsingHqlFetches( "Eve", scope );
|
||||
verifyEve( eve );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Frank
|
||||
// - 1 active detail
|
||||
// - 2 deleted and 2 active skills
|
||||
|
||||
/**
|
||||
* Create the user Frank with a single active detail and 2 deleted and 2 active skills
|
||||
*/
|
||||
private static void createFrank(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (entityManager) -> {
|
||||
User user = new User( 1, "Frank" );
|
||||
entityManager.persist( user );
|
||||
|
||||
UserDetail detail = new UserDetail( 1, "Madrid", true, user );
|
||||
entityManager.persist( detail );
|
||||
|
||||
UserSkill skill1 = new UserSkill( 1, "Rust", true, user );
|
||||
entityManager.persist( skill1 );
|
||||
|
||||
UserSkill skill2 = new UserSkill( 2, "Erlang", false, user );
|
||||
entityManager.persist( skill2 );
|
||||
|
||||
UserSkill skill3 = new UserSkill( 3, "Go", false, user );
|
||||
entityManager.persist( skill3 );
|
||||
|
||||
UserSkill skill4 = new UserSkill( 4, "C", true, user );
|
||||
entityManager.persist( skill4 );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void verifyFrank(User frank) {
|
||||
assertNotNull( frank );
|
||||
assertEquals("Frank", frank.getName());
|
||||
|
||||
assertNotNull( frank.getDetail());
|
||||
assertTrue( frank.getDetail().getActive());
|
||||
assertEquals("Madrid", frank.getDetail().getCity());
|
||||
|
||||
assertFalse( frank.getSkills().isEmpty());
|
||||
assertTrue( frank.getSkills().stream().noneMatch(UserSkill::getDeleted));
|
||||
assertEquals(2, frank.getSkills().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindFrankById(SessionFactoryScope scope) {
|
||||
createFrank( scope );
|
||||
|
||||
User frank = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyFrank( frank );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindFrankByHqlWithGraph(SessionFactoryScope scope) {
|
||||
createFrank( scope );
|
||||
|
||||
User frank = findUserByNameUsingEntityGraph( "Frank", scope );
|
||||
verifyFrank( frank );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindFrankByHqlWithJoinFetch(SessionFactoryScope scope) {
|
||||
createFrank( scope );
|
||||
|
||||
User frank = findUserByNameUsingHqlFetches( "Frank", scope );
|
||||
verifyFrank( frank );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Grace
|
||||
// - 1 active and 1 inactive details
|
||||
// - 4 inactive skills
|
||||
|
||||
private static void createGrace(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (entityManager) -> {
|
||||
User user = new User( 1, "Grace" );
|
||||
entityManager.persist( user );
|
||||
|
||||
UserDetail detail1 = new UserDetail( 1, "Vienna", false, user );
|
||||
entityManager.persist( detail1 );
|
||||
|
||||
UserDetail detail2 = new UserDetail( 2, "Barcelona", true, user );
|
||||
entityManager.persist( detail2 );
|
||||
|
||||
UserSkill skill1 = new UserSkill( 1, "PHP", false, user );
|
||||
entityManager.persist( skill1 );
|
||||
|
||||
UserSkill skill2 = new UserSkill( 2, "Swift", false, user );
|
||||
entityManager.persist( skill2 );
|
||||
|
||||
UserSkill skill3 = new UserSkill( 3, "Dart", false, user );
|
||||
entityManager.persist( skill3 );
|
||||
|
||||
UserSkill skill4 = new UserSkill( 4, "Scala", false, user );
|
||||
entityManager.persist( skill4 );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void verifyGrace(User grace) {
|
||||
assertNotNull( grace );
|
||||
assertEquals("Grace", grace.getName());
|
||||
|
||||
assertNotNull( grace.getDetail());
|
||||
assertTrue( grace.getDetail().getActive());
|
||||
assertEquals("Barcelona", grace.getDetail().getCity());
|
||||
|
||||
assertFalse( grace.getSkills().isEmpty());
|
||||
assertTrue( grace.getSkills().stream().noneMatch(UserSkill::getDeleted));
|
||||
assertEquals(4, grace.getSkills().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindGraceById(SessionFactoryScope scope) {
|
||||
createGrace( scope );
|
||||
|
||||
User grace = findUserByIdUsingEntityGraph( 1, scope );
|
||||
verifyGrace( grace );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindGraceByHqlWithGraph(SessionFactoryScope scope) {
|
||||
createGrace( scope );
|
||||
|
||||
User grace = findUserByNameUsingEntityGraph( "Grace", scope );
|
||||
verifyGrace( grace );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindGraceByHqlWithJoinFetch(SessionFactoryScope scope) {
|
||||
createGrace( scope );
|
||||
|
||||
User grace = findUserByNameUsingHqlFetches( "Grace", scope );
|
||||
verifyGrace( grace );
|
||||
}
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Zurg
|
||||
// - 1 active and 1 inactive detail
|
||||
// - 1 active and 1 inactive skill
|
||||
|
||||
private void createZurg(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final User user = new User( 1, "Zurg" );
|
||||
session.persist( user );
|
||||
|
||||
UserDetail detail1 = new UserDetail( 1, "Infinity", false, user );
|
||||
session.persist( detail1 );
|
||||
|
||||
UserDetail detail2 = new UserDetail( 2, "Beyond", true, user );
|
||||
session.persist( detail2 );
|
||||
|
||||
UserSkill skill1 = new UserSkill( 1, "Plundering", false, user );
|
||||
session.persist( skill1 );
|
||||
|
||||
UserSkill skill2 = new UserSkill( 2, "Pillaging", true, user );
|
||||
session.persist( skill2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadEntity(SessionFactoryScope scope) {
|
||||
createZurg( scope );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final UserSkill skill1 = session.find( UserSkill.class, 1 );
|
||||
assertThat( skill1 ).isNotNull();
|
||||
|
||||
final UserSkill skill2 = session.find( UserSkill.class, 2 );
|
||||
assertThat( skill2 ).isNull();
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final UserDetail detail1 = session.find( UserDetail.class, 1 );
|
||||
assertThat( detail1 ).isNull();
|
||||
|
||||
final UserDetail detail2 = session.find( UserDetail.class, 2 );
|
||||
assertThat( detail2 ).isNotNull();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubsequentInitialization(SessionFactoryScope scope) {
|
||||
createZurg( scope );
|
||||
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
User user = session.find( User.class, 1 );
|
||||
|
||||
// this should have initialized User & User#detail in 2 separate selects
|
||||
assertThat( Hibernate.isInitialized( user.getDetail() ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( user.getSkills() ) ).isFalse();
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||
|
||||
// trigger load of User#skills
|
||||
statementInspector.clear();
|
||||
Hibernate.initialize( user.getSkills() );
|
||||
assertThat( Hibernate.isInitialized( user.getSkills() ) ).isTrue();
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( user.getSkills() ).hasSize( 1 );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -45,11 +45,6 @@ public class EagerToManyWhereUseClassWhereViaAnnotationTest extends BaseNonConfi
|
|||
return new Class[] { Product.class, Category.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addSettings(Map<String,Object> settings) {
|
||||
settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-15936" )
|
||||
public void testAssociatedWhereClause() {
|
||||
|
@ -198,7 +193,7 @@ public class EagerToManyWhereUseClassWhereViaAnnotationTest extends BaseNonConfi
|
|||
|
||||
@Entity(name = "Category")
|
||||
@Table(name = "CATEGORY")
|
||||
@Where(clause = "inactive = 0", applyInToManyFetch = true)
|
||||
@Where(clause = "inactive = 0")
|
||||
public static class Category {
|
||||
@Id
|
||||
private int id;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Needed because the test model maps a "non standard" schema.
|
||||
The test models `User#detail` and `UserDetail#user` as a
|
||||
bi-directional one-to-one, meaning that Hibernate would
|
||||
normally (and correctly) create `t_user_details.user_fk` as
|
||||
unique. The test model changes that cardinality by use of
|
||||
`@Where`.
|
||||
*/
|
||||
drop table if exists t_user_details cascade;
|
||||
drop table if exists t_user_skills cascade;
|
||||
drop table if exists t_users cascade;
|
||||
create table t_user_details (
|
||||
detail_id integer not null,
|
||||
is_active boolean,
|
||||
city varchar(255),
|
||||
user_fk integer,
|
||||
primary key (detail_id)
|
||||
);
|
||||
create table t_user_skills (
|
||||
skill_id integer not null,
|
||||
has_deleted boolean,
|
||||
skill_name varchar(255),
|
||||
user_fk integer,
|
||||
primary key (skill_id)
|
||||
);
|
||||
create table t_users (
|
||||
user_id integer not null,
|
||||
user_name varchar(255),
|
||||
primary key (user_id)
|
||||
);
|
Loading…
Reference in New Issue