HHH-16830 Ensure filters applied for by key lookups don't mess with to-one associations

This commit is contained in:
Christian Beikov 2024-05-27 19:31:23 +02:00
parent 4125902eea
commit be8705f317
31 changed files with 426 additions and 143 deletions

View File

@ -102,5 +102,5 @@ public interface Filter {
* *
* @return The flag value * @return The flag value
*/ */
boolean isApplyToLoadById(); boolean isApplyToLoadByKey();
} }

View File

@ -10,6 +10,8 @@ import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.hibernate.Incubating;
import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -101,7 +103,8 @@ public @interface FilterDef {
* be applied on direct fetches or not. * be applied on direct fetches or not.
* <p> * <p>
* If the flag is true, the filter will be * If the flag is true, the filter will be
* applied on direct fetches, such as findById(). * applied on direct fetches, such as {@link org.hibernate.Session#find(Class, Object)}.
*/ */
boolean applyToLoadById() default false; @Incubating
boolean applyToLoadByKey() default false;
} }

View File

@ -59,7 +59,7 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
"", "",
singletonMap( PARAMETER_NAME, tenantIdType ), singletonMap( PARAMETER_NAME, tenantIdType ),
Collections.emptyMap(), Collections.emptyMap(),
true, false,
true true
) )
); );

View File

@ -93,7 +93,7 @@ class FilterDefBinder {
explicitParamJaMappings, explicitParamJaMappings,
parameterResolvers, parameterResolvers,
filterDef.autoEnabled(), filterDef.autoEnabled(),
filterDef.applyToLoadById() filterDef.applyToLoadByKey()
); );
LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() ); LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() );
context.getMetadataCollector().addFilterDefinition( filterDefinition ); context.getMetadataCollector().addFilterDefinition( filterDefinition );

View File

@ -37,30 +37,37 @@ public class FilterDefinition implements Serializable {
private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>(); private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>();
private final Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap = new HashMap<>(); private final Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap = new HashMap<>();
private final boolean autoEnabled; private final boolean autoEnabled;
private final boolean applyToLoadById; private final boolean applyToLoadByKey;
/** /**
* Construct a new FilterDefinition instance. * Construct a new FilterDefinition instance.
* *
* @param name The name of the filter for which this configuration is in effect. * @param name The name of the filter for which this configuration is in effect.
*/ */
public FilterDefinition(String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings) { public FilterDefinition(
String name,
String defaultCondition,
@Nullable Map<String, JdbcMapping> explicitParamJaMappings) {
this( name, defaultCondition, explicitParamJaMappings, Collections.emptyMap(), false, false); this( name, defaultCondition, explicitParamJaMappings, Collections.emptyMap(), false, false);
} }
public FilterDefinition( public FilterDefinition(
String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings, String name,
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap, boolean autoEnabled, boolean applyToLoadById) { String defaultCondition,
@Nullable Map<String, JdbcMapping> explicitParamJaMappings,
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap,
boolean autoEnabled,
boolean applyToLoadByKey) {
this.filterName = name; this.filterName = name;
this.defaultFilterCondition = defaultCondition; this.defaultFilterCondition = defaultCondition;
if ( explicitParamJaMappings != null ) { if ( explicitParamJaMappings != null ) {
this.explicitParamJaMappings.putAll( explicitParamJaMappings ); this.explicitParamJaMappings.putAll( explicitParamJaMappings );
} }
this.applyToLoadById = applyToLoadById;
if ( parameterResolverMap != null ) { if ( parameterResolverMap != null ) {
this.parameterResolverMap.putAll( parameterResolverMap ); this.parameterResolverMap.putAll( parameterResolverMap );
} }
this.autoEnabled = autoEnabled; this.autoEnabled = autoEnabled;
this.applyToLoadByKey = applyToLoadByKey;
} }
/** /**
@ -109,8 +116,8 @@ public class FilterDefinition implements Serializable {
* *
* @return The flag value. * @return The flag value.
*/ */
public boolean isApplyToLoadById() { public boolean isApplyToLoadByKey() {
return applyToLoadById; return applyToLoadByKey;
} }
/** /**

View File

@ -13,7 +13,6 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.hibernate.Filter; import org.hibernate.Filter;
import org.hibernate.Internal; import org.hibernate.Internal;
@ -50,10 +49,10 @@ public class LoadQueryInfluencers implements Serializable {
private CascadingFetchProfile enabledCascadingFetchProfile; private CascadingFetchProfile enabledCascadingFetchProfile;
//Lazily initialized! //Lazily initialized!
private HashSet<String> enabledFetchProfileNames; private @Nullable HashSet<String> enabledFetchProfileNames;
//Lazily initialized! //Lazily initialized!
private HashMap<String,Filter> enabledFilters; private @Nullable HashMap<String,Filter> enabledFilters;
private boolean subselectFetchEnabled; private boolean subselectFetchEnabled;
@ -76,12 +75,37 @@ public class LoadQueryInfluencers implements Serializable {
for (FilterDefinition filterDefinition : sessionFactory.getAutoEnabledFilters()) { for (FilterDefinition filterDefinition : sessionFactory.getAutoEnabledFilters()) {
FilterImpl filter = new FilterImpl( filterDefinition ); FilterImpl filter = new FilterImpl( filterDefinition );
if ( enabledFilters == null ) { if ( enabledFilters == null ) {
this.enabledFilters = new HashMap<>(); enabledFilters = new HashMap<>();
} }
enabledFilters.put( filterDefinition.getFilterName(), filter ); enabledFilters.put( filterDefinition.getFilterName(), filter );
} }
} }
/**
* Special constructor for {@link #copyForLoadByKey()}.
*/
private LoadQueryInfluencers(LoadQueryInfluencers original) {
this.sessionFactory = original.sessionFactory;
this.enabledCascadingFetchProfile = original.enabledCascadingFetchProfile;
this.enabledFetchProfileNames = original.enabledFetchProfileNames == null ? null : new HashSet<>( original.enabledFetchProfileNames );
this.subselectFetchEnabled = original.subselectFetchEnabled;
this.batchSize = original.batchSize;
this.readOnly = original.readOnly;
HashMap<String,Filter> enabledFilters;
if ( original.enabledFilters == null ) {
enabledFilters = null;
}
else {
enabledFilters = new HashMap<>( original.enabledFilters.size() );
for ( Map.Entry<String, Filter> entry : original.enabledFilters.entrySet() ) {
if ( entry.getValue().isApplyToLoadByKey() ) {
enabledFilters.put( entry.getKey(), entry.getValue() );
}
}
}
this.enabledFilters = enabledFilters;
}
public EffectiveEntityGraph applyEntityGraph(@Nullable RootGraphImplementor<?> rootGraph, @Nullable GraphSemantic graphSemantic) { public EffectiveEntityGraph applyEntityGraph(@Nullable RootGraphImplementor<?> rootGraph, @Nullable GraphSemantic graphSemantic) {
final EffectiveEntityGraph effectiveEntityGraph = getEffectiveEntityGraph(); final EffectiveEntityGraph effectiveEntityGraph = getEffectiveEntityGraph();
if ( graphSemantic != null ) { if ( graphSemantic != null ) {
@ -156,6 +180,7 @@ public class LoadQueryInfluencers implements Serializable {
} }
public Map<String,Filter> getEnabledFilters() { public Map<String,Filter> getEnabledFilters() {
final HashMap<String, Filter> enabledFilters = this.enabledFilters;
if ( enabledFilters == null ) { if ( enabledFilters == null ) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
@ -169,20 +194,6 @@ public class LoadQueryInfluencers implements Serializable {
} }
} }
/**
* Returns a Map of enabled filters that have the applyToLoadById
* flag set to true
* @return a Map of enabled filters that have the applyToLoadById
* flag set to true
*/
public Map<String, Filter> getEnabledFiltersForFind() {
return getEnabledFilters()
.entrySet()
.stream()
.filter(f -> f.getValue().isApplyToLoadById())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/** /**
* Returns an unmodifiable Set of enabled filter names. * Returns an unmodifiable Set of enabled filter names.
* @return an unmodifiable Set of enabled filter names. * @return an unmodifiable Set of enabled filter names.
@ -283,8 +294,14 @@ public class LoadQueryInfluencers implements Serializable {
@Internal @Internal
public @Nullable HashSet<String> adjustFetchProfiles(@Nullable Set<String> disabledFetchProfiles, @Nullable Set<String> enabledFetchProfiles) { public @Nullable HashSet<String> adjustFetchProfiles(@Nullable Set<String> disabledFetchProfiles, @Nullable Set<String> enabledFetchProfiles) {
final HashSet<String> oldFetchProfiles = final HashSet<String> currentEnabledFetchProfileNames = this.enabledFetchProfileNames;
hasEnabledFetchProfiles() ? new HashSet<>( enabledFetchProfileNames ) : null; final HashSet<String> oldFetchProfiles;
if ( currentEnabledFetchProfileNames == null || currentEnabledFetchProfileNames.isEmpty() ) {
oldFetchProfiles = null;
}
else {
oldFetchProfiles = new HashSet<>( currentEnabledFetchProfileNames );
}
if ( disabledFetchProfiles != null && enabledFetchProfileNames != null ) { if ( disabledFetchProfiles != null && enabledFetchProfileNames != null ) {
enabledFetchProfileNames.removeAll( disabledFetchProfiles ); enabledFetchProfileNames.removeAll( disabledFetchProfiles );
} }
@ -391,4 +408,7 @@ public class LoadQueryInfluencers implements Serializable {
return false; return false;
} }
public LoadQueryInfluencers copyForLoadByKey() {
return new LoadQueryInfluencers( this );
}
} }

View File

@ -123,6 +123,10 @@ public class FilterHelper {
return aliasTableMap.size() == 1 && aliasTableMap.containsKey( null ); return aliasTableMap.size() == 1 && aliasTableMap.containsKey( null );
} }
public String[] getFilterNames() {
return filterNames;
}
public boolean isAffectedBy(Map<String, Filter> enabledFilters) { public boolean isAffectedBy(Map<String, Filter> enabledFilters) {
for ( String filterName : filterNames ) { for ( String filterName : filterNames ) {
if ( enabledFilters.containsKey( filterName ) ) { if ( enabledFilters.containsKey( filterName ) ) {
@ -132,6 +136,16 @@ public class FilterHelper {
return false; return false;
} }
public boolean isAffectedByApplyToLoadByKey(Map<String, Filter> enabledFilters) {
for ( String filterName : filterNames ) {
Filter filter = enabledFilters.get( filterName );
if ( filter != null && filter.isApplyToLoadByKey() ) {
return true;
}
}
return false;
}
public static void applyBaseRestrictions( public static void applyBaseRestrictions(
Consumer<Predicate> predicateConsumer, Consumer<Predicate> predicateConsumer,
Restrictable restrictable, Restrictable restrictable,

View File

@ -32,7 +32,7 @@ public class FilterImpl implements Filter, Serializable {
private final String filterName; private final String filterName;
private final Map<String,Object> parameters = new HashMap<>(); private final Map<String,Object> parameters = new HashMap<>();
private final boolean autoEnabled; private final boolean autoEnabled;
private final boolean applyToLoadById; private final boolean applyToLoadByKey;
void afterDeserialize(SessionFactoryImplementor factory) { void afterDeserialize(SessionFactoryImplementor factory) {
definition = factory.getFilterDefinition( filterName ); definition = factory.getFilterDefinition( filterName );
@ -48,7 +48,7 @@ public class FilterImpl implements Filter, Serializable {
this.definition = configuration; this.definition = configuration;
filterName = definition.getFilterName(); filterName = definition.getFilterName();
this.autoEnabled = definition.isAutoEnabled(); this.autoEnabled = definition.isAutoEnabled();
this.applyToLoadById = definition.isApplyToLoadById(); this.applyToLoadByKey = definition.isApplyToLoadByKey();
} }
public FilterDefinition getFilterDefinition() { public FilterDefinition getFilterDefinition() {
@ -80,8 +80,8 @@ public class FilterImpl implements Filter, Serializable {
* *
* @return The flag value. * @return The flag value.
*/ */
public boolean isApplyToLoadById() { public boolean isApplyToLoadByKey() {
return applyToLoadById; return applyToLoadByKey;
} }
public Map<String,?> getParameters() { public Map<String,?> getParameters() {

View File

@ -10,7 +10,7 @@ import org.hibernate.Hibernate;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.EntityBatchLoader; import org.hibernate.loader.ast.spi.EntityBatchLoader;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
@ -24,9 +24,9 @@ public abstract class AbstractEntityBatchLoader<T>
private final SingleIdEntityLoaderStandardImpl<T> singleIdLoader; private final SingleIdEntityLoaderStandardImpl<T> singleIdLoader;
public AbstractEntityBatchLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { public AbstractEntityBatchLoader(EntityMappingType entityDescriptor, LoadQueryInfluencers loadQueryInfluencers) {
super( entityDescriptor, sessionFactory ); super( entityDescriptor, loadQueryInfluencers.getSessionFactory() );
singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, sessionFactory ); this.singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, loadQueryInfluencers );
} }
protected abstract void initializeEntities( protected abstract void initializeEntities(

View File

@ -42,6 +42,7 @@ public class EntityBatchLoaderArrayParam<T>
implements SqlArrayMultiKeyLoader { implements SqlArrayMultiKeyLoader {
private final int domainBatchSize; private final int domainBatchSize;
private final LoadQueryInfluencers loadQueryInfluencers;
private final BasicEntityIdentifierMapping identifierMapping; private final BasicEntityIdentifierMapping identifierMapping;
private final JdbcMapping arrayJdbcMapping; private final JdbcMapping arrayJdbcMapping;
private final JdbcParameter jdbcParameter; private final JdbcParameter jdbcParameter;
@ -63,8 +64,9 @@ public class EntityBatchLoaderArrayParam<T>
public EntityBatchLoaderArrayParam( public EntityBatchLoaderArrayParam(
int domainBatchSize, int domainBatchSize,
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory) { LoadQueryInfluencers loadQueryInfluencers) {
super( entityDescriptor, sessionFactory ); super( entityDescriptor, loadQueryInfluencers );
this.loadQueryInfluencers = loadQueryInfluencers;
this.domainBatchSize = domainBatchSize; this.domainBatchSize = domainBatchSize;
if ( MULTI_KEY_LOAD_LOGGER.isDebugEnabled() ) { if ( MULTI_KEY_LOAD_LOGGER.isDebugEnabled() ) {
@ -89,7 +91,7 @@ public class EntityBatchLoaderArrayParam<T>
sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter( sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter(
getLoadable(), getLoadable(),
identifierMapping, identifierMapping,
new LoadQueryInfluencers( sessionFactory ), loadQueryInfluencers,
LockOptions.NONE, LockOptions.NONE,
jdbcParameter, jdbcParameter,
sessionFactory sessionFactory

View File

@ -46,6 +46,7 @@ public class EntityBatchLoaderInPredicate<T>
private final int domainBatchSize; private final int domainBatchSize;
private final int sqlBatchSize; private final int sqlBatchSize;
private final LoadQueryInfluencers loadQueryInfluencers;
private final JdbcParametersList jdbcParameters; private final JdbcParametersList jdbcParameters;
private final SelectStatement sqlAst; private final SelectStatement sqlAst;
private final JdbcOperationQuerySelect jdbcSelectOperation; private final JdbcOperationQuerySelect jdbcSelectOperation;
@ -56,8 +57,9 @@ public class EntityBatchLoaderInPredicate<T>
public EntityBatchLoaderInPredicate( public EntityBatchLoaderInPredicate(
int domainBatchSize, int domainBatchSize,
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory) { LoadQueryInfluencers loadQueryInfluencers) {
super( entityDescriptor, sessionFactory ); super( entityDescriptor, loadQueryInfluencers );
this.loadQueryInfluencers = loadQueryInfluencers;
this.domainBatchSize = domainBatchSize; this.domainBatchSize = domainBatchSize;
int idColumnCount = entityDescriptor.getEntityPersister().getIdentifierType().getColumnSpan( sessionFactory ); int idColumnCount = entityDescriptor.getEntityPersister().getIdentifierType().getColumnSpan( sessionFactory );
this.sqlBatchSize = sessionFactory.getJdbcServices() this.sqlBatchSize = sessionFactory.getJdbcServices()
@ -85,7 +87,7 @@ public class EntityBatchLoaderInPredicate<T>
identifierMapping, identifierMapping,
null, null,
sqlBatchSize, sqlBatchSize,
new LoadQueryInfluencers( sessionFactory ), loadQueryInfluencers,
LockOptions.NONE, LockOptions.NONE,
jdbcParametersBuilder::add, jdbcParametersBuilder::add,
sessionFactory sessionFactory

View File

@ -30,6 +30,7 @@ import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
@ -67,6 +68,7 @@ import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate; import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.InListPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.PredicateContainer;
import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -708,16 +710,15 @@ public class LoaderSelectBuilder {
} }
private void applyFiltering( private void applyFiltering(
QuerySpec querySpec, PredicateContainer predicateContainer,
TableGroup tableGroup, TableGroup tableGroup,
Restrictable restrictable, Restrictable restrictable,
SqlAstCreationState astCreationState) { SqlAstCreationState astCreationState) {
restrictable.applyBaseRestrictions( restrictable.applyBaseRestrictions(
querySpec::applyPredicate, predicateContainer::applyPredicate,
tableGroup, tableGroup,
true, true,
// HHH-16830 Session.find should apply filters only if specified on the filter definition loadQueryInfluencers.getEnabledFilters(),
loadQueryInfluencers.getEnabledFiltersForFind(),
null, null,
astCreationState astCreationState
); );
@ -962,9 +963,9 @@ public class LoaderSelectBuilder {
creationState creationState
); );
if ( fetch.getTiming() == FetchTiming.IMMEDIATE && isFetchablePluralAttributeMapping ) { if ( fetch.getTiming() == FetchTiming.IMMEDIATE && joined ) {
if ( isFetchablePluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
if ( joined ) {
final TableGroup joinTableGroup = creationState.getFromClauseAccess() final TableGroup joinTableGroup = creationState.getFromClauseAccess()
.getTableGroup( fetchablePath ); .getTableGroup( fetchablePath );
final QuerySpec querySpec = creationState.getInflightQueryPart().getFirstQuerySpec(); final QuerySpec querySpec = creationState.getInflightQueryPart().getFirstQuerySpec();
@ -981,6 +982,19 @@ public class LoaderSelectBuilder {
creationState creationState
); );
} }
else if ( fetchable instanceof ToOneAttributeMapping ) {
final EntityMappingType entityType = ( (ToOneAttributeMapping) fetchable ).getEntityMappingType();
final FromClauseAccess fromClauseAccess = creationState.getFromClauseAccess();
final TableGroup joinTableGroup = fromClauseAccess.getTableGroup( fetchablePath );
final TableGroupJoin join = fromClauseAccess.getTableGroup( fetchParent.getNavigablePath() )
.findTableGroupJoin( joinTableGroup );
applyFiltering(
join,
joinTableGroup,
entityType,
creationState
);
}
} }
fetches.add( fetch ); fetches.add( fetch );

View File

@ -30,7 +30,6 @@ import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
@ -220,7 +219,7 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
getLoadable().getIdentifierMapping(), getLoadable().getIdentifierMapping(),
null, null,
numberOfIdsInBatch, numberOfIdsInBatch,
session.getLoadQueryInfluencers(), session.getLoadQueryInfluencers().copyForLoadByKey(),
lockOptions, lockOptions,
jdbcParametersBuilder::add, jdbcParametersBuilder::add,
getSessionFactory() getSessionFactory()

View File

@ -34,11 +34,11 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
public SingleIdEntityLoaderStandardImpl( public SingleIdEntityLoaderStandardImpl(
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory) { LoadQueryInfluencers loadQueryInfluencers) {
this( this(
entityDescriptor, entityDescriptor,
sessionFactory, loadQueryInfluencers,
(lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, sessionFactory ) (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() )
); );
} }
@ -50,15 +50,14 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
*/ */
protected SingleIdEntityLoaderStandardImpl( protected SingleIdEntityLoaderStandardImpl(
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor sessionFactory, LoadQueryInfluencers influencers,
BiFunction<LockOptions, LoadQueryInfluencers, SingleIdLoadPlan<T>> loadPlanCreator) { BiFunction<LockOptions, LoadQueryInfluencers, SingleIdLoadPlan<T>> loadPlanCreator) {
// todo (6.0) : consider creating a base AST and "cloning" it // todo (6.0) : consider creating a base AST and "cloning" it
super( entityDescriptor, sessionFactory ); super( entityDescriptor, influencers.getSessionFactory() );
this.loadPlanCreator = loadPlanCreator; this.loadPlanCreator = loadPlanCreator;
// see org.hibernate.persister.entity.AbstractEntityPersister#createLoaders // see org.hibernate.persister.entity.AbstractEntityPersister#createLoaders
// we should preload a few - maybe LockMode.NONE and LockMode.READ // we should preload a few - maybe LockMode.NONE and LockMode.READ
final LockOptions lockOptions = LockOptions.NONE; final LockOptions lockOptions = LockOptions.NONE;
final LoadQueryInfluencers influencers = new LoadQueryInfluencers( sessionFactory );
final SingleIdLoadPlan<T> plan = loadPlanCreator.apply( LockOptions.NONE, influencers ); final SingleIdLoadPlan<T> plan = loadPlanCreator.apply( LockOptions.NONE, influencers );
if ( isLoadPlanReusable( lockOptions, influencers ) ) { if ( isLoadPlanReusable( lockOptions, influencers ) ) {
selectByLockMode.put( lockOptions.getLockMode(), plan ); selectByLockMode.put( lockOptions.getLockMode(), plan );

View File

@ -25,7 +25,6 @@ import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -52,7 +51,8 @@ public class SingleUniqueKeyEntityLoaderStandard<T> implements SingleUniqueKeyEn
public SingleUniqueKeyEntityLoaderStandard( public SingleUniqueKeyEntityLoaderStandard(
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SingularAttributeMapping uniqueKeyAttribute) { SingularAttributeMapping uniqueKeyAttribute,
LoadQueryInfluencers loadQueryInfluencers) {
this.entityDescriptor = entityDescriptor; this.entityDescriptor = entityDescriptor;
this.uniqueKeyAttributePath = getAttributePath( uniqueKeyAttribute ); this.uniqueKeyAttributePath = getAttributePath( uniqueKeyAttribute );
if ( uniqueKeyAttribute instanceof ToOneAttributeMapping ) { if ( uniqueKeyAttribute instanceof ToOneAttributeMapping ) {
@ -69,7 +69,7 @@ public class SingleUniqueKeyEntityLoaderStandard<T> implements SingleUniqueKeyEn
Collections.emptyList(), Collections.emptyList(),
uniqueKeyAttribute, uniqueKeyAttribute,
null, null,
new LoadQueryInfluencers( sessionFactory ), loadQueryInfluencers,
LockOptions.NONE, LockOptions.NONE,
builder::add, builder::add,
sessionFactory sessionFactory

View File

@ -33,19 +33,20 @@ public class StandardBatchLoaderFactory implements BatchLoaderFactory {
@Override @Override
public <T> EntityBatchLoader<T> createEntityBatchLoader( public <T> EntityBatchLoader<T> createEntityBatchLoader(
int domainBatchSize, EntityMappingType entityDescriptor, int domainBatchSize,
SessionFactoryImplementor factory) { EntityMappingType entityDescriptor,
LoadQueryInfluencers loadQueryInfluencers) {
final SessionFactoryImplementor factory = loadQueryInfluencers.getSessionFactory();
// NOTE : don't use the EntityIdentifierMapping here because it will not be known until later // NOTE : don't use the EntityIdentifierMapping here because it will not be known until later
final Type identifierType = entityDescriptor.getEntityPersister().getIdentifierType(); final Type identifierType = entityDescriptor.getEntityPersister().getIdentifierType();
if ( identifierType.getColumnSpan( factory ) == 1 if ( identifierType.getColumnSpan( factory ) == 1
&& supportsSqlArrayType( factory.getJdbcServices().getDialect() ) && supportsSqlArrayType( factory.getJdbcServices().getDialect() )
&& identifierType instanceof BasicType ) { && identifierType instanceof BasicType ) {
// we can use a single ARRAY parameter to send all the ids // we can use a single ARRAY parameter to send all the ids
return new EntityBatchLoaderArrayParam<>( domainBatchSize, entityDescriptor, factory ); return new EntityBatchLoaderArrayParam<>( domainBatchSize, entityDescriptor, loadQueryInfluencers );
} }
else { else {
return new EntityBatchLoaderInPredicate<>( domainBatchSize, entityDescriptor, factory ); return new EntityBatchLoaderInPredicate<>( domainBatchSize, entityDescriptor, loadQueryInfluencers );
} }
} }

View File

@ -18,6 +18,21 @@ import org.hibernate.service.Service;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface BatchLoaderFactory extends Service { public interface BatchLoaderFactory extends Service {
/**
* Create a BatchLoader for batch-loadable entities.
*
* @param domainBatchSize The total number of entities (max) that will be need to be initialized
* @param entityDescriptor The entity mapping metadata
* @deprecated Use {@link #createEntityBatchLoader(int, EntityMappingType, LoadQueryInfluencers)} instead
*/
@Deprecated(forRemoval = true)
default <T> EntityBatchLoader<T> createEntityBatchLoader(
int domainBatchSize,
EntityMappingType entityDescriptor,
SessionFactoryImplementor factory) {
return createEntityBatchLoader( domainBatchSize, entityDescriptor, new LoadQueryInfluencers( factory ) );
}
/** /**
* Create a BatchLoader for batch-loadable entities. * Create a BatchLoader for batch-loadable entities.
* *
@ -27,7 +42,7 @@ public interface BatchLoaderFactory extends Service {
<T> EntityBatchLoader<T> createEntityBatchLoader( <T> EntityBatchLoader<T> createEntityBatchLoader(
int domainBatchSize, int domainBatchSize,
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
SessionFactoryImplementor factory); LoadQueryInfluencers loadQueryInfluencers);
/** /**
* Create a BatchLoader for batch-loadable collections. * Create a BatchLoader for batch-loadable collections.

View File

@ -35,6 +35,13 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
|| isAffectedByBatchSize( influencers ); || isAffectedByBatchSize( influencers );
} }
default boolean isAffectedByInfluencersForLoadByKey(LoadQueryInfluencers influencers) {
return isAffectedByEntityGraph( influencers )
|| isAffectedByEnabledFetchProfiles( influencers )
|| isAffectedByEnabledFiltersForLoadByKey( influencers )
|| isAffectedByBatchSize( influencers );
}
default boolean isNotAffectedByInfluencers(LoadQueryInfluencers influencers) { default boolean isNotAffectedByInfluencers(LoadQueryInfluencers influencers) {
return !isAffectedByEntityGraph( influencers ) return !isAffectedByEntityGraph( influencers )
&& !isAffectedByEnabledFetchProfiles( influencers ) && !isAffectedByEnabledFetchProfiles( influencers )
@ -55,6 +62,13 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
*/ */
boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers); boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers);
/**
* Whether any of the "influencers" affect this loadable.
*/
default boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
return isAffectedByInfluencers( influencers.copyForLoadByKey() );
}
/** /**
* Whether the {@linkplain LoadQueryInfluencers#getEffectiveEntityGraph() effective entity-graph} * Whether the {@linkplain LoadQueryInfluencers#getEffectiveEntityGraph() effective entity-graph}
* applies to this loadable * applies to this loadable

View File

@ -547,6 +547,11 @@ public interface EntityMappingType
return getEntityPersister().isAffectedByEnabledFilters( influencers ); return getEntityPersister().isAffectedByEnabledFilters( influencers );
} }
@Override
default boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
return getEntityPersister().isAffectedByEnabledFiltersForLoadByKey( influencers );
}
@Override @Override
default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
return getEntityPersister().isAffectedByEntityGraph( influencers ); return getEntityPersister().isAffectedByEntityGraph( influencers );

View File

@ -1077,6 +1077,11 @@ public class PluralAttributeMappingImpl
return getCollectionDescriptor().isAffectedByEnabledFilters( influencers ); return getCollectionDescriptor().isAffectedByEnabledFilters( influencers );
} }
@Override
public boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
return getCollectionDescriptor().isAffectedByEnabledFiltersForLoadByKey( influencers );
}
@Override @Override
public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
return getCollectionDescriptor().isAffectedByEntityGraph( influencers ); return getCollectionDescriptor().isAffectedByEntityGraph( influencers );

View File

@ -248,7 +248,7 @@ public class ToOneAttributeMapping
stateArrayPosition, stateArrayPosition,
fetchableIndex, fetchableIndex,
attributeMetadata, attributeMetadata,
adjustFetchTiming( mappedFetchTiming, bootValue ), adjustFetchTiming( mappedFetchTiming, bootValue, entityMappingType ),
mappedFetchStyle, mappedFetchStyle,
declaringType, declaringType,
propertyAccess propertyAccess
@ -270,7 +270,7 @@ public class ToOneAttributeMapping
); );
if ( bootValue instanceof ManyToOne ) { if ( bootValue instanceof ManyToOne ) {
final ManyToOne manyToOne = (ManyToOne) bootValue; final ManyToOne manyToOne = (ManyToOne) bootValue;
this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction(); this.notFoundAction = determineNotFoundAction( ( (ManyToOne) bootValue ).getNotFoundAction(), entityMappingType );
if ( manyToOne.isLogicalOneToOne() ) { if ( manyToOne.isLogicalOneToOne() ) {
cardinality = Cardinality.LOGICAL_ONE_TO_ONE; cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
} }
@ -424,7 +424,7 @@ public class ToOneAttributeMapping
else { else {
this.bidirectionalAttributePath = SelectablePath.parse( oneToOne.getMappedByProperty() ); this.bidirectionalAttributePath = SelectablePath.parse( oneToOne.getMappedByProperty() );
} }
notFoundAction = null; notFoundAction = determineNotFoundAction( null, entityMappingType );
isKeyTableNullable = isNullable(); isKeyTableNullable = isNullable();
isOptional = !bootValue.isConstrained(); isOptional = !bootValue.isConstrained();
isInternalLoadNullable = isNullable(); isInternalLoadNullable = isNullable();
@ -569,6 +569,16 @@ public class ToOneAttributeMapping
} }
} }
private NotFoundAction determineNotFoundAction(NotFoundAction notFoundAction, EntityMappingType entityMappingType) {
// When a filter exists that affects a singular association, we have to enable NotFound handling
// to force an exception if the filter would result in the entity not being found.
// If we silently just read null, this could lead to data loss on flush
if ( entityMappingType.getEntityPersister().hasFilterForLoadByKey() && notFoundAction == null ) {
return NotFoundAction.EXCEPTION;
}
return notFoundAction;
}
private static SelectablePath findBidirectionalOneToManyAttributeName( private static SelectablePath findBidirectionalOneToManyAttributeName(
String propertyPath, String propertyPath,
ManagedMappingType declaringType, ManagedMappingType declaringType,
@ -638,12 +648,18 @@ public class ToOneAttributeMapping
return null; return null;
} }
private static FetchTiming adjustFetchTiming(FetchTiming mappedFetchTiming, ToOne bootValue) { private static FetchTiming adjustFetchTiming(
FetchTiming mappedFetchTiming,
ToOne bootValue,
EntityMappingType entityMappingType) {
if ( bootValue instanceof ManyToOne ) { if ( bootValue instanceof ManyToOne ) {
if ( ( (ManyToOne) bootValue ).getNotFoundAction() != null ) { if ( ( (ManyToOne) bootValue ).getNotFoundAction() != null ) {
return FetchTiming.IMMEDIATE; return FetchTiming.IMMEDIATE;
} }
} }
if ( entityMappingType.getEntityPersister().hasFilterForLoadByKey() ) {
return FetchTiming.IMMEDIATE;
}
return mappedFetchTiming; return mappedFetchTiming;
} }

View File

@ -1677,6 +1677,18 @@ public abstract class AbstractCollectionPersister
} }
} }
@Override
public boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
if ( influencers.hasEnabledFilters() ) {
final Map<String, Filter> enabledFilters = influencers.getEnabledFilters();
return filterHelper != null && filterHelper.isAffectedByApplyToLoadByKey( enabledFilters )
|| manyToManyFilterHelper != null && manyToManyFilterHelper.isAffectedByApplyToLoadByKey( enabledFilters );
}
else {
return false;
}
}
@Override @Override
public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
// todo (6.0) : anything to do here? // todo (6.0) : anything to do here?

View File

@ -296,6 +296,10 @@ public interface CollectionPersister extends Restrictable {
throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" );
} }
default boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" );
}
default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" );
} }

View File

@ -17,7 +17,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -26,6 +25,7 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -536,6 +536,16 @@ public abstract class AbstractEntityPersister
? MutableEntityEntryFactory.INSTANCE ? MutableEntityEntryFactory.INSTANCE
: ImmutableEntityEntryFactory.INSTANCE; : ImmutableEntityEntryFactory.INSTANCE;
// Handle any filters applied to the class level
filterHelper = isNotEmpty( persistentClass.getFilters() ) ? new FilterHelper(
persistentClass.getFilters(),
getEntityNameByTableNameMap(
persistentClass,
factory.getSqlStringGenerationContext()
),
factory
) : null;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
representationStrategy = creationContext.getBootstrapContext().getRepresentationStrategySelector() representationStrategy = creationContext.getBootstrapContext().getRepresentationStrategySelector()
@ -804,16 +814,6 @@ public abstract class AbstractEntityPersister
propertyDefinedOnSubclass = toBooleanArray( definedBySubclass ); propertyDefinedOnSubclass = toBooleanArray( definedBySubclass );
// Handle any filters applied to the class level
filterHelper = isNotEmpty( persistentClass.getFilters() ) ? new FilterHelper(
persistentClass.getFilters(),
getEntityNameByTableNameMap(
persistentClass,
factory.getSqlStringGenerationContext()
),
factory
) : null;
useReferenceCacheEntries = shouldUseReferenceCacheEntries( creationContext.getSessionFactoryOptions() ); useReferenceCacheEntries = shouldUseReferenceCacheEntries( creationContext.getSessionFactoryOptions() );
useShallowQueryCacheLayout = shouldUseShallowCacheLayout( useShallowQueryCacheLayout = shouldUseShallowCacheLayout(
persistentClass.getQueryCacheLayout(), persistentClass.getQueryCacheLayout(),
@ -884,10 +884,10 @@ public abstract class AbstractEntityPersister
final int batchSize = loadQueryInfluencers.effectiveBatchSize( this ); final int batchSize = loadQueryInfluencers.effectiveBatchSize( this );
return factory.getServiceRegistry() return factory.getServiceRegistry()
.requireService( BatchLoaderFactory.class ) .requireService( BatchLoaderFactory.class )
.createEntityBatchLoader( batchSize, this, factory ); .createEntityBatchLoader( batchSize, this, loadQueryInfluencers );
} }
else { else {
return new SingleIdEntityLoaderStandardImpl<>( this, factory ); return new SingleIdEntityLoaderStandardImpl<>( this, loadQueryInfluencers );
} }
} }
@ -1255,6 +1255,18 @@ public abstract class AbstractEntityPersister
return storeDiscriminatorInShallowQueryCacheLayout; return storeDiscriminatorInShallowQueryCacheLayout;
} }
@Override
public boolean hasFilterForLoadByKey() {
if ( filterHelper != null ) {
for ( String filterName : filterHelper.getFilterNames() ) {
if ( factory.getFilterDefinition( filterName ).isApplyToLoadByKey() ) {
return true;
}
}
}
return false;
}
@Override @Override
public Iterable<UniqueKeyEntry> uniqueKeyEntries() { public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
if ( this.uniqueKeyEntries == null ) { if ( this.uniqueKeyEntries == null ) {
@ -2039,7 +2051,7 @@ public abstract class AbstractEntityPersister
); );
} }
return getUniqueKeyLoader( uniquePropertyName ).resolveId( key, session ); return getUniqueKeyLoader( uniquePropertyName, session ).resolveId( key, session );
} }
@ -2652,16 +2664,25 @@ public abstract class AbstractEntityPersister
Object uniqueKey, Object uniqueKey,
Boolean readOnly, Boolean readOnly,
SharedSessionContractImplementor session) throws HibernateException { SharedSessionContractImplementor session) throws HibernateException {
return getUniqueKeyLoader( propertyName ).load( uniqueKey, LockOptions.NONE, readOnly, session ); return getUniqueKeyLoader( propertyName, session ).load( uniqueKey, LockOptions.NONE, readOnly, session );
} }
private Map<SingularAttributeMapping, SingleUniqueKeyEntityLoader<?>> uniqueKeyLoadersNew; private Map<SingularAttributeMapping, SingleUniqueKeyEntityLoader<?>> uniqueKeyLoadersNew;
protected SingleUniqueKeyEntityLoader<?> getUniqueKeyLoader(String attributeName) { protected SingleUniqueKeyEntityLoader<?> getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) {
final SingularAttributeMapping attribute = (SingularAttributeMapping) findByPath( attributeName ); final SingularAttributeMapping attribute = (SingularAttributeMapping) findByPath( attributeName );
final LoadQueryInfluencers influencers = session.getLoadQueryInfluencers();
// no subselect fetching for entities for now
if ( isAffectedByInfluencersForLoadByKey( influencers ) ) {
return new SingleUniqueKeyEntityLoaderStandard<>(
this,
attribute,
influencers.copyForLoadByKey()
);
}
final SingleUniqueKeyEntityLoader<?> existing; final SingleUniqueKeyEntityLoader<?> existing;
if ( uniqueKeyLoadersNew == null ) { if ( uniqueKeyLoadersNew == null ) {
uniqueKeyLoadersNew = new IdentityHashMap<>(); uniqueKeyLoadersNew = new ConcurrentHashMap<>();
existing = null; existing = null;
} }
else { else {
@ -2673,7 +2694,7 @@ public abstract class AbstractEntityPersister
} }
else { else {
final SingleUniqueKeyEntityLoader<?> loader = final SingleUniqueKeyEntityLoader<?> loader =
new SingleUniqueKeyEntityLoaderStandard<>( this, attribute ); new SingleUniqueKeyEntityLoaderStandard<>( this, attribute, new LoadQueryInfluencers( factory ) );
uniqueKeyLoadersNew.put( attribute, loader ); uniqueKeyLoadersNew.put( attribute, loader );
return loader; return loader;
} }
@ -3744,8 +3765,8 @@ public abstract class AbstractEntityPersister
else { else {
final LoadQueryInfluencers influencers = session.getLoadQueryInfluencers(); final LoadQueryInfluencers influencers = session.getLoadQueryInfluencers();
// no subselect fetching for entities for now // no subselect fetching for entities for now
return isAffectedByInfluencers( influencers ) return isAffectedByInfluencersForLoadByKey( influencers )
? buildSingleIdEntityLoader( influencers ) ? buildSingleIdEntityLoader( influencers.copyForLoadByKey() )
: getSingleIdLoader(); : getSingleIdLoader();
} }
} }
@ -3863,6 +3884,30 @@ public abstract class AbstractEntityPersister
return false; return false;
} }
@Override
public boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers loadQueryInfluencers) {
if ( filterHelper != null && loadQueryInfluencers.hasEnabledFilters() ) {
if ( filterHelper.isAffectedByApplyToLoadByKey( loadQueryInfluencers.getEnabledFilters() ) ) {
return true;
}
// we still need to verify collection fields to be eagerly loaded by join
final AttributeMappingsList attributeMappings = getAttributeMappings();
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attributeMapping = attributeMappings.get( i );
if ( attributeMapping instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) attributeMapping;
if ( pluralAttributeMapping.getMappedFetchOptions().getTiming() == FetchTiming.IMMEDIATE
&& pluralAttributeMapping.getMappedFetchOptions().getStyle() == FetchStyle.JOIN
&& pluralAttributeMapping.getCollectionDescriptor().isAffectedByEnabledFiltersForLoadByKey( loadQueryInfluencers ) ) {
return true;
}
}
}
}
return false;
}
@Override @Override
public boolean isSubclassPropertyNullable(int i) { public boolean isSubclassPropertyNullable(int i) {
return subclassPropertyNullabilityClosure[i]; return subclassPropertyNullabilityClosure[i];

View File

@ -1258,6 +1258,8 @@ public interface EntityPersister extends EntityMappingType, EntityMutationTarget
@Incubating @Incubating
boolean storeDiscriminatorInShallowQueryCacheLayout(); boolean storeDiscriminatorInShallowQueryCacheLayout();
boolean hasFilterForLoadByKey();
/** /**
* The property name of the "special" identifier property in HQL * The property name of the "special" identifier property in HQL
* *

View File

@ -14,13 +14,14 @@ import org.hibernate.sql.ast.SqlTreeCreationLogger;
import org.hibernate.sql.ast.spi.SqlAstTreeHelper; import org.hibernate.sql.ast.spi.SqlAstTreeHelper;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.predicate.PredicateContainer;
import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class TableGroupJoin implements TableJoin, DomainResultProducer { public class TableGroupJoin implements TableJoin, PredicateContainer, DomainResultProducer {
private final NavigablePath navigablePath; private final NavigablePath navigablePath;
private final TableGroup joinedGroup; private final TableGroup joinedGroup;
@ -78,6 +79,7 @@ public class TableGroupJoin implements TableJoin, DomainResultProducer {
return predicate; return predicate;
} }
@Override
public void applyPredicate(Predicate predicate) { public void applyPredicate(Predicate predicate) {
this.predicate = SqlAstTreeHelper.combinePredicates( this.predicate, predicate ); this.predicate = SqlAstTreeHelper.combinePredicates( this.predicate, predicate );
} }

View File

@ -760,6 +760,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
return false; return false;
} }
@Override
public boolean hasFilterForLoadByKey() {
return false;
}
@Override @Override
public Iterable<UniqueKeyEntry> uniqueKeyEntries() { public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -54,7 +54,7 @@ public class LoadPlanBuilderTest {
.getMappingMetamodel() .getMappingMetamodel()
.getEntityDescriptor( Message.class ); .getEntityDescriptor( Message.class );
final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, sessionFactory ); final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( sessionFactory ) );
final SingleIdLoadPlan<?> loadPlan = loader.resolveLoadPlan( final SingleIdLoadPlan<?> loadPlan = loader.resolveLoadPlan(
LockOptions.READ, LockOptions.READ,
@ -89,7 +89,7 @@ public class LoadPlanBuilderTest {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final EntityPersister entityDescriptor = (EntityPersister) sessionFactory.getRuntimeMetamodels().getEntityMappingType( Message.class ); final EntityPersister entityDescriptor = (EntityPersister) sessionFactory.getRuntimeMetamodels().getEntityMappingType( Message.class );
final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, sessionFactory ); final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( sessionFactory ) );
final LoadQueryInfluencers influencers = new LoadQueryInfluencers( sessionFactory ) { final LoadQueryInfluencers influencers = new LoadQueryInfluencers( sessionFactory ) {
@Override @Override

View File

@ -739,6 +739,11 @@ public class PersisterClassProviderTest {
return false; return false;
} }
@Override
public boolean hasFilterForLoadByKey() {
return false;
}
@Override @Override
public Iterable<UniqueKeyEntry> uniqueKeyEntries() { public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -896,6 +896,11 @@ public class CustomPersister implements EntityPersister {
return false; return false;
} }
@Override
public boolean hasFilterForLoadByKey() {
return false;
}
@Override @Override
public Iterable<UniqueKeyEntry> uniqueKeyEntries() { public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -14,6 +14,7 @@ import java.util.function.Supplier;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
@ -23,13 +24,18 @@ import jakarta.persistence.NoResultException;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.FetchNotFoundException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef; import org.hibernate.annotations.ParamDef;
import org.hibernate.jpa.AvailableHints;
import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.hibernate.cfg.AvailableSettings.DEFAULT_LIST_SEMANTICS; import static org.hibernate.cfg.AvailableSettings.DEFAULT_LIST_SEMANTICS;
@ -38,6 +44,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
/** /**
* @author Vlad Mihalcea * @author Vlad Mihalcea
@ -58,18 +66,18 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
options.put( DEFAULT_LIST_SEMANTICS, CollectionClassification.BAG.name() ); options.put( DEFAULT_LIST_SEMANTICS, CollectionClassification.BAG.name() );
} }
@Test @Before
public void testLifecycle() { public void setup() {
doInJPA(this::entityManagerFactory, entityManager -> { doInJPA(this::entityManagerFactory, entityManager -> {
//tag::pc-filter-persistence-example[] //tag::pc-filter-persistence-example[]
Client client = new Client() Client client = new Client()
.setId(1L) .setId(1L)
.setName("John Doe") .setName("John Doe")
.setType(AccountType.DEBIT); .setType(AccountType.DEBIT);
Account account1;
client.addAccount( client.addAccount(
new Account() account1 = new Account()
.setId(1L) .setId(1L)
.setType(AccountType.CREDIT) .setType(AccountType.CREDIT)
.setAmount(5000d) .setAmount(5000d)
@ -84,6 +92,7 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
.setAmount(0d) .setAmount(0d)
.setRate(1.05 / 100) .setRate(1.05 / 100)
.setActive(false) .setActive(false)
.setParentAccount( account1 )
); );
client.addAccount( client.addAccount(
@ -98,7 +107,18 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
entityManager.persist(client); entityManager.persist(client);
//end::pc-filter-persistence-example[] //end::pc-filter-persistence-example[]
}); });
}
@After
public void tearDown() {
doInJPA(this::entityManagerFactory, entityManager -> {
entityManager.createQuery( "delete from Account" ).executeUpdate();
entityManager.createQuery( "delete from Client" ).executeUpdate();
});
}
@Test
public void testLifecycle() {
doInJPA(this::entityManagerFactory, entityManager -> { doInJPA(this::entityManagerFactory, entityManager -> {
log.infof("Activate filter [%s]", "activeAccount"); log.infof("Activate filter [%s]", "activeAccount");
@ -177,6 +197,33 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
//end::pc-filter-entity-query-example[] //end::pc-filter-entity-query-example[]
}); });
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::pc-no-filter-collection-query-example[]
Client client = entityManager.find(Client.class, 1L);
assertEquals(3, client.getAccounts().size());
//end::pc-no-filter-collection-query-example[]
});
doInJPA(this::entityManagerFactory, entityManager -> {
log.infof("Activate filter [%s]", "activeAccount");
//tag::pc-filter-collection-query-example[]
entityManager
.unwrap(Session.class)
.enableFilter("activeAccount")
.setParameter("active", true);
Client client = entityManager.find(Client.class, 1L);
assertEquals(2, client.getAccounts().size());
//end::pc-filter-collection-query-example[]
});
}
@Test
@JiraKey("HHH-16830")
public void testApplyToLoadByKey() {
doInJPA(this::entityManagerFactory, entityManager -> { doInJPA(this::entityManagerFactory, entityManager -> {
log.infof("Activate filter [%s]", "minimumAmount"); log.infof("Activate filter [%s]", "minimumAmount");
//tag::pc-filter-entity-example[] //tag::pc-filter-entity-example[]
@ -220,28 +267,44 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
assertEquals(1, accounts.size()); assertEquals(1, accounts.size());
//end::pc-filter-entity-query-example[] //end::pc-filter-entity-query-example[]
}); });
}
@Test
@JiraKey("HHH-16830")
public void testApplyToLoadByKeyAssociationFiltering() {
doInJPA(this::entityManagerFactory, entityManager -> { doInJPA(this::entityManagerFactory, entityManager -> {
//tag::pc-no-filter-collection-query-example[] Account account = entityManager.find(Account.class, 2L);
Client client = entityManager.find(Client.class, 1L); assertNotNull( account.getParentAccount() );
assertEquals(3, client.getAccounts().size());
//end::pc-no-filter-collection-query-example[]
}); });
doInJPA(this::entityManagerFactory, entityManager -> { doInJPA(this::entityManagerFactory, entityManager -> {
log.infof("Activate filter [%s]", "activeAccount"); entityManager.unwrap(Session.class)
.enableFilter("accountType")
.setParameter("type", "DEBIT");
//tag::pc-filter-collection-query-example[] FetchNotFoundException exception = assertThrows(
entityManager FetchNotFoundException.class,
.unwrap(Session.class) () -> entityManager.find( Account.class, 2L )
.enableFilter("activeAccount") );
.setParameter("active", true); // Account with id 1 does not exist
assertTrue( exception.getMessage().contains( "`1`" ) );
});
doInJPA(this::entityManagerFactory, entityManager -> {
entityManager.unwrap(Session.class)
.enableFilter("accountType")
.setParameter("type", "DEBIT");
EntityGraph<Account> entityGraph = entityManager.createEntityGraph( Account.class );
entityGraph.addAttributeNodes( "parentAccount" );
Client client = entityManager.find(Client.class, 1L); FetchNotFoundException exception = assertThrows(
FetchNotFoundException.class,
assertEquals(2, client.getAccounts().size()); () -> entityManager.find(
//end::pc-filter-collection-query-example[] Account.class,
2L,
Map.of( AvailableHints.HINT_SPEC_LOAD_GRAPH, entityGraph )
)
);
// Account with id 1 does not exist
assertTrue( exception.getMessage().contains( "`1`" ) );
}); });
} }
@ -333,12 +396,24 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
name="amount", name="amount",
type=Double.class type=Double.class
), ),
applyToLoadById = true applyToLoadByKey = true
) )
@Filter( @Filter(
name="minimumAmount", name="minimumAmount",
condition="amount > :amount" condition="amount > :amount"
) )
@FilterDef(
name="accountType",
parameters = @ParamDef(
name="type",
type=String.class
),
applyToLoadByKey = true
)
@Filter(
name="accountType",
condition="account_type = :type"
)
public static class Account { public static class Account {
@ -361,6 +436,10 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
//Getters and setters omitted for brevity //Getters and setters omitted for brevity
//end::pc-filter-Account-example[] //end::pc-filter-Account-example[]
@ManyToOne(fetch = FetchType.LAZY)
private Account parentAccount;
public Long getId() { public Long getId() {
return id; return id;
} }
@ -415,6 +494,14 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
return this; return this;
} }
public Account getParentAccount() {
return parentAccount;
}
public Account setParentAccount(Account parentAccount) {
this.parentAccount = parentAccount;
return this;
}
//tag::pc-filter-Account-example[] //tag::pc-filter-Account-example[]
} }
//end::pc-filter-Account-example[] //end::pc-filter-Account-example[]