HHH-16830 Ensure filters applied for by key lookups don't mess with to-one associations
This commit is contained in:
parent
4125902eea
commit
be8705f317
|
@ -102,5 +102,5 @@ public interface Filter {
|
|||
*
|
||||
* @return The flag value
|
||||
*/
|
||||
boolean isApplyToLoadById();
|
||||
boolean isApplyToLoadByKey();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import java.lang.annotation.Repeatable;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
import static java.lang.annotation.ElementType.PACKAGE;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
@ -101,7 +103,8 @@ public @interface FilterDef {
|
|||
* be applied on direct fetches or not.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
|
|||
"",
|
||||
singletonMap( PARAMETER_NAME, tenantIdType ),
|
||||
Collections.emptyMap(),
|
||||
true,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
|
|
@ -93,7 +93,7 @@ class FilterDefBinder {
|
|||
explicitParamJaMappings,
|
||||
parameterResolvers,
|
||||
filterDef.autoEnabled(),
|
||||
filterDef.applyToLoadById()
|
||||
filterDef.applyToLoadByKey()
|
||||
);
|
||||
LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() );
|
||||
context.getMetadataCollector().addFilterDefinition( filterDefinition );
|
||||
|
|
|
@ -37,30 +37,37 @@ public class FilterDefinition implements Serializable {
|
|||
private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>();
|
||||
private final Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap = new HashMap<>();
|
||||
private final boolean autoEnabled;
|
||||
private final boolean applyToLoadById;
|
||||
private final boolean applyToLoadByKey;
|
||||
|
||||
/**
|
||||
* Construct a new FilterDefinition instance.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
public FilterDefinition(
|
||||
String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings,
|
||||
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap, boolean autoEnabled, boolean applyToLoadById) {
|
||||
String name,
|
||||
String defaultCondition,
|
||||
@Nullable Map<String, JdbcMapping> explicitParamJaMappings,
|
||||
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap,
|
||||
boolean autoEnabled,
|
||||
boolean applyToLoadByKey) {
|
||||
this.filterName = name;
|
||||
this.defaultFilterCondition = defaultCondition;
|
||||
if ( explicitParamJaMappings != null ) {
|
||||
this.explicitParamJaMappings.putAll( explicitParamJaMappings );
|
||||
}
|
||||
this.applyToLoadById = applyToLoadById;
|
||||
if ( parameterResolverMap != null ) {
|
||||
this.parameterResolverMap.putAll( parameterResolverMap );
|
||||
}
|
||||
this.autoEnabled = autoEnabled;
|
||||
this.applyToLoadByKey = applyToLoadByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,8 +116,8 @@ public class FilterDefinition implements Serializable {
|
|||
*
|
||||
* @return The flag value.
|
||||
*/
|
||||
public boolean isApplyToLoadById() {
|
||||
return applyToLoadById;
|
||||
public boolean isApplyToLoadByKey() {
|
||||
return applyToLoadByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.HashSet;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.hibernate.Filter;
|
||||
import org.hibernate.Internal;
|
||||
|
@ -50,10 +49,10 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
private CascadingFetchProfile enabledCascadingFetchProfile;
|
||||
|
||||
//Lazily initialized!
|
||||
private HashSet<String> enabledFetchProfileNames;
|
||||
private @Nullable HashSet<String> enabledFetchProfileNames;
|
||||
|
||||
//Lazily initialized!
|
||||
private HashMap<String,Filter> enabledFilters;
|
||||
private @Nullable HashMap<String,Filter> enabledFilters;
|
||||
|
||||
private boolean subselectFetchEnabled;
|
||||
|
||||
|
@ -76,12 +75,37 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
for (FilterDefinition filterDefinition : sessionFactory.getAutoEnabledFilters()) {
|
||||
FilterImpl filter = new FilterImpl( filterDefinition );
|
||||
if ( enabledFilters == null ) {
|
||||
this.enabledFilters = new HashMap<>();
|
||||
enabledFilters = new HashMap<>();
|
||||
}
|
||||
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) {
|
||||
final EffectiveEntityGraph effectiveEntityGraph = getEffectiveEntityGraph();
|
||||
if ( graphSemantic != null ) {
|
||||
|
@ -156,6 +180,7 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
}
|
||||
|
||||
public Map<String,Filter> getEnabledFilters() {
|
||||
final HashMap<String, Filter> enabledFilters = this.enabledFilters;
|
||||
if ( enabledFilters == null ) {
|
||||
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.
|
||||
* @return an unmodifiable Set of enabled filter names.
|
||||
|
@ -283,8 +294,14 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
|
||||
@Internal
|
||||
public @Nullable HashSet<String> adjustFetchProfiles(@Nullable Set<String> disabledFetchProfiles, @Nullable Set<String> enabledFetchProfiles) {
|
||||
final HashSet<String> oldFetchProfiles =
|
||||
hasEnabledFetchProfiles() ? new HashSet<>( enabledFetchProfileNames ) : null;
|
||||
final HashSet<String> currentEnabledFetchProfileNames = this.enabledFetchProfileNames;
|
||||
final HashSet<String> oldFetchProfiles;
|
||||
if ( currentEnabledFetchProfileNames == null || currentEnabledFetchProfileNames.isEmpty() ) {
|
||||
oldFetchProfiles = null;
|
||||
}
|
||||
else {
|
||||
oldFetchProfiles = new HashSet<>( currentEnabledFetchProfileNames );
|
||||
}
|
||||
if ( disabledFetchProfiles != null && enabledFetchProfileNames != null ) {
|
||||
enabledFetchProfileNames.removeAll( disabledFetchProfiles );
|
||||
}
|
||||
|
@ -391,4 +408,7 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
return false;
|
||||
}
|
||||
|
||||
public LoadQueryInfluencers copyForLoadByKey() {
|
||||
return new LoadQueryInfluencers( this );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,10 @@ public class FilterHelper {
|
|||
return aliasTableMap.size() == 1 && aliasTableMap.containsKey( null );
|
||||
}
|
||||
|
||||
public String[] getFilterNames() {
|
||||
return filterNames;
|
||||
}
|
||||
|
||||
public boolean isAffectedBy(Map<String, Filter> enabledFilters) {
|
||||
for ( String filterName : filterNames ) {
|
||||
if ( enabledFilters.containsKey( filterName ) ) {
|
||||
|
@ -132,6 +136,16 @@ public class FilterHelper {
|
|||
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(
|
||||
Consumer<Predicate> predicateConsumer,
|
||||
Restrictable restrictable,
|
||||
|
|
|
@ -32,7 +32,7 @@ public class FilterImpl implements Filter, Serializable {
|
|||
private final String filterName;
|
||||
private final Map<String,Object> parameters = new HashMap<>();
|
||||
private final boolean autoEnabled;
|
||||
private final boolean applyToLoadById;
|
||||
private final boolean applyToLoadByKey;
|
||||
|
||||
void afterDeserialize(SessionFactoryImplementor factory) {
|
||||
definition = factory.getFilterDefinition( filterName );
|
||||
|
@ -48,7 +48,7 @@ public class FilterImpl implements Filter, Serializable {
|
|||
this.definition = configuration;
|
||||
filterName = definition.getFilterName();
|
||||
this.autoEnabled = definition.isAutoEnabled();
|
||||
this.applyToLoadById = definition.isApplyToLoadById();
|
||||
this.applyToLoadByKey = definition.isApplyToLoadByKey();
|
||||
}
|
||||
|
||||
public FilterDefinition getFilterDefinition() {
|
||||
|
@ -80,8 +80,8 @@ public class FilterImpl implements Filter, Serializable {
|
|||
*
|
||||
* @return The flag value.
|
||||
*/
|
||||
public boolean isApplyToLoadById() {
|
||||
return applyToLoadById;
|
||||
public boolean isApplyToLoadByKey() {
|
||||
return applyToLoadByKey;
|
||||
}
|
||||
|
||||
public Map<String,?> getParameters() {
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.hibernate.Hibernate;
|
|||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
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.loader.ast.spi.EntityBatchLoader;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
|
@ -24,9 +24,9 @@ public abstract class AbstractEntityBatchLoader<T>
|
|||
|
||||
private final SingleIdEntityLoaderStandardImpl<T> singleIdLoader;
|
||||
|
||||
public AbstractEntityBatchLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) {
|
||||
super( entityDescriptor, sessionFactory );
|
||||
singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, sessionFactory );
|
||||
public AbstractEntityBatchLoader(EntityMappingType entityDescriptor, LoadQueryInfluencers loadQueryInfluencers) {
|
||||
super( entityDescriptor, loadQueryInfluencers.getSessionFactory() );
|
||||
this.singleIdLoader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, loadQueryInfluencers );
|
||||
}
|
||||
|
||||
protected abstract void initializeEntities(
|
||||
|
|
|
@ -42,6 +42,7 @@ public class EntityBatchLoaderArrayParam<T>
|
|||
implements SqlArrayMultiKeyLoader {
|
||||
private final int domainBatchSize;
|
||||
|
||||
private final LoadQueryInfluencers loadQueryInfluencers;
|
||||
private final BasicEntityIdentifierMapping identifierMapping;
|
||||
private final JdbcMapping arrayJdbcMapping;
|
||||
private final JdbcParameter jdbcParameter;
|
||||
|
@ -63,8 +64,9 @@ public class EntityBatchLoaderArrayParam<T>
|
|||
public EntityBatchLoaderArrayParam(
|
||||
int domainBatchSize,
|
||||
EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
super( entityDescriptor, sessionFactory );
|
||||
LoadQueryInfluencers loadQueryInfluencers) {
|
||||
super( entityDescriptor, loadQueryInfluencers );
|
||||
this.loadQueryInfluencers = loadQueryInfluencers;
|
||||
this.domainBatchSize = domainBatchSize;
|
||||
|
||||
if ( MULTI_KEY_LOAD_LOGGER.isDebugEnabled() ) {
|
||||
|
@ -89,7 +91,7 @@ public class EntityBatchLoaderArrayParam<T>
|
|||
sqlAst = LoaderSelectBuilder.createSelectBySingleArrayParameter(
|
||||
getLoadable(),
|
||||
identifierMapping,
|
||||
new LoadQueryInfluencers( sessionFactory ),
|
||||
loadQueryInfluencers,
|
||||
LockOptions.NONE,
|
||||
jdbcParameter,
|
||||
sessionFactory
|
||||
|
|
|
@ -46,6 +46,7 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
private final int domainBatchSize;
|
||||
private final int sqlBatchSize;
|
||||
|
||||
private final LoadQueryInfluencers loadQueryInfluencers;
|
||||
private final JdbcParametersList jdbcParameters;
|
||||
private final SelectStatement sqlAst;
|
||||
private final JdbcOperationQuerySelect jdbcSelectOperation;
|
||||
|
@ -56,8 +57,9 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
public EntityBatchLoaderInPredicate(
|
||||
int domainBatchSize,
|
||||
EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
super( entityDescriptor, sessionFactory );
|
||||
LoadQueryInfluencers loadQueryInfluencers) {
|
||||
super( entityDescriptor, loadQueryInfluencers );
|
||||
this.loadQueryInfluencers = loadQueryInfluencers;
|
||||
this.domainBatchSize = domainBatchSize;
|
||||
int idColumnCount = entityDescriptor.getEntityPersister().getIdentifierType().getColumnSpan( sessionFactory );
|
||||
this.sqlBatchSize = sessionFactory.getJdbcServices()
|
||||
|
@ -85,7 +87,7 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
identifierMapping,
|
||||
null,
|
||||
sqlBatchSize,
|
||||
new LoadQueryInfluencers( sessionFactory ),
|
||||
loadQueryInfluencers,
|
||||
LockOptions.NONE,
|
||||
jdbcParametersBuilder::add,
|
||||
sessionFactory
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.hibernate.metamodel.CollectionClassification;
|
|||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
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.InListPredicate;
|
||||
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.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
|
@ -708,16 +710,15 @@ public class LoaderSelectBuilder {
|
|||
}
|
||||
|
||||
private void applyFiltering(
|
||||
QuerySpec querySpec,
|
||||
PredicateContainer predicateContainer,
|
||||
TableGroup tableGroup,
|
||||
Restrictable restrictable,
|
||||
SqlAstCreationState astCreationState) {
|
||||
restrictable.applyBaseRestrictions(
|
||||
querySpec::applyPredicate,
|
||||
predicateContainer::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
// HHH-16830 Session.find should apply filters only if specified on the filter definition
|
||||
loadQueryInfluencers.getEnabledFiltersForFind(),
|
||||
loadQueryInfluencers.getEnabledFilters(),
|
||||
null,
|
||||
astCreationState
|
||||
);
|
||||
|
@ -962,9 +963,9 @@ public class LoaderSelectBuilder {
|
|||
creationState
|
||||
);
|
||||
|
||||
if ( fetch.getTiming() == FetchTiming.IMMEDIATE && isFetchablePluralAttributeMapping ) {
|
||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
|
||||
if ( joined ) {
|
||||
if ( fetch.getTiming() == FetchTiming.IMMEDIATE && joined ) {
|
||||
if ( isFetchablePluralAttributeMapping ) {
|
||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
|
||||
final TableGroup joinTableGroup = creationState.getFromClauseAccess()
|
||||
.getTableGroup( fetchablePath );
|
||||
final QuerySpec querySpec = creationState.getInflightQueryPart().getFirstQuerySpec();
|
||||
|
@ -981,6 +982,19 @@ public class LoaderSelectBuilder {
|
|||
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 );
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
|
|||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
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.exec.internal.JdbcParameterBindingsImpl;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
||||
|
@ -220,7 +219,7 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
|
|||
getLoadable().getIdentifierMapping(),
|
||||
null,
|
||||
numberOfIdsInBatch,
|
||||
session.getLoadQueryInfluencers(),
|
||||
session.getLoadQueryInfluencers().copyForLoadByKey(),
|
||||
lockOptions,
|
||||
jdbcParametersBuilder::add,
|
||||
getSessionFactory()
|
||||
|
|
|
@ -34,11 +34,11 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
|
|||
|
||||
public SingleIdEntityLoaderStandardImpl(
|
||||
EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
LoadQueryInfluencers loadQueryInfluencers) {
|
||||
this(
|
||||
entityDescriptor,
|
||||
sessionFactory,
|
||||
(lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, sessionFactory )
|
||||
loadQueryInfluencers,
|
||||
(lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,15 +50,14 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
|
|||
*/
|
||||
protected SingleIdEntityLoaderStandardImpl(
|
||||
EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor sessionFactory,
|
||||
LoadQueryInfluencers influencers,
|
||||
BiFunction<LockOptions, LoadQueryInfluencers, SingleIdLoadPlan<T>> loadPlanCreator) {
|
||||
// todo (6.0) : consider creating a base AST and "cloning" it
|
||||
super( entityDescriptor, sessionFactory );
|
||||
super( entityDescriptor, influencers.getSessionFactory() );
|
||||
this.loadPlanCreator = loadPlanCreator;
|
||||
// see org.hibernate.persister.entity.AbstractEntityPersister#createLoaders
|
||||
// we should preload a few - maybe LockMode.NONE and LockMode.READ
|
||||
final LockOptions lockOptions = LockOptions.NONE;
|
||||
final LoadQueryInfluencers influencers = new LoadQueryInfluencers( sessionFactory );
|
||||
final SingleIdLoadPlan<T> plan = loadPlanCreator.apply( LockOptions.NONE, influencers );
|
||||
if ( isLoadPlanReusable( lockOptions, influencers ) ) {
|
||||
selectByLockMode.put( lockOptions.getLockMode(), plan );
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.hibernate.metamodel.mapping.ManagedMappingType;
|
|||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
|
@ -52,7 +51,8 @@ public class SingleUniqueKeyEntityLoaderStandard<T> implements SingleUniqueKeyEn
|
|||
|
||||
public SingleUniqueKeyEntityLoaderStandard(
|
||||
EntityMappingType entityDescriptor,
|
||||
SingularAttributeMapping uniqueKeyAttribute) {
|
||||
SingularAttributeMapping uniqueKeyAttribute,
|
||||
LoadQueryInfluencers loadQueryInfluencers) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
this.uniqueKeyAttributePath = getAttributePath( uniqueKeyAttribute );
|
||||
if ( uniqueKeyAttribute instanceof ToOneAttributeMapping ) {
|
||||
|
@ -69,7 +69,7 @@ public class SingleUniqueKeyEntityLoaderStandard<T> implements SingleUniqueKeyEn
|
|||
Collections.emptyList(),
|
||||
uniqueKeyAttribute,
|
||||
null,
|
||||
new LoadQueryInfluencers( sessionFactory ),
|
||||
loadQueryInfluencers,
|
||||
LockOptions.NONE,
|
||||
builder::add,
|
||||
sessionFactory
|
||||
|
|
|
@ -33,19 +33,20 @@ public class StandardBatchLoaderFactory implements BatchLoaderFactory {
|
|||
|
||||
@Override
|
||||
public <T> EntityBatchLoader<T> createEntityBatchLoader(
|
||||
int domainBatchSize, EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor factory) {
|
||||
|
||||
int domainBatchSize,
|
||||
EntityMappingType entityDescriptor,
|
||||
LoadQueryInfluencers loadQueryInfluencers) {
|
||||
final SessionFactoryImplementor factory = loadQueryInfluencers.getSessionFactory();
|
||||
// NOTE : don't use the EntityIdentifierMapping here because it will not be known until later
|
||||
final Type identifierType = entityDescriptor.getEntityPersister().getIdentifierType();
|
||||
if ( identifierType.getColumnSpan( factory ) == 1
|
||||
&& supportsSqlArrayType( factory.getJdbcServices().getDialect() )
|
||||
&& identifierType instanceof BasicType ) {
|
||||
// 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 {
|
||||
return new EntityBatchLoaderInPredicate<>( domainBatchSize, entityDescriptor, factory );
|
||||
return new EntityBatchLoaderInPredicate<>( domainBatchSize, entityDescriptor, loadQueryInfluencers );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,21 @@ import org.hibernate.service.Service;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -27,7 +42,7 @@ public interface BatchLoaderFactory extends Service {
|
|||
<T> EntityBatchLoader<T> createEntityBatchLoader(
|
||||
int domainBatchSize,
|
||||
EntityMappingType entityDescriptor,
|
||||
SessionFactoryImplementor factory);
|
||||
LoadQueryInfluencers loadQueryInfluencers);
|
||||
|
||||
/**
|
||||
* Create a BatchLoader for batch-loadable collections.
|
||||
|
|
|
@ -35,6 +35,13 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
|
|||
|| isAffectedByBatchSize( influencers );
|
||||
}
|
||||
|
||||
default boolean isAffectedByInfluencersForLoadByKey(LoadQueryInfluencers influencers) {
|
||||
return isAffectedByEntityGraph( influencers )
|
||||
|| isAffectedByEnabledFetchProfiles( influencers )
|
||||
|| isAffectedByEnabledFiltersForLoadByKey( influencers )
|
||||
|| isAffectedByBatchSize( influencers );
|
||||
}
|
||||
|
||||
default boolean isNotAffectedByInfluencers(LoadQueryInfluencers influencers) {
|
||||
return !isAffectedByEntityGraph( influencers )
|
||||
&& !isAffectedByEnabledFetchProfiles( influencers )
|
||||
|
@ -55,6 +62,13 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
|
|||
*/
|
||||
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}
|
||||
* applies to this loadable
|
||||
|
|
|
@ -547,6 +547,11 @@ public interface EntityMappingType
|
|||
return getEntityPersister().isAffectedByEnabledFilters( influencers );
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
|
||||
return getEntityPersister().isAffectedByEnabledFiltersForLoadByKey( influencers );
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
|
||||
return getEntityPersister().isAffectedByEntityGraph( influencers );
|
||||
|
|
|
@ -1077,6 +1077,11 @@ public class PluralAttributeMappingImpl
|
|||
return getCollectionDescriptor().isAffectedByEnabledFilters( influencers );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffectedByEnabledFiltersForLoadByKey(LoadQueryInfluencers influencers) {
|
||||
return getCollectionDescriptor().isAffectedByEnabledFiltersForLoadByKey( influencers );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
|
||||
return getCollectionDescriptor().isAffectedByEntityGraph( influencers );
|
||||
|
|
|
@ -248,7 +248,7 @@ public class ToOneAttributeMapping
|
|||
stateArrayPosition,
|
||||
fetchableIndex,
|
||||
attributeMetadata,
|
||||
adjustFetchTiming( mappedFetchTiming, bootValue ),
|
||||
adjustFetchTiming( mappedFetchTiming, bootValue, entityMappingType ),
|
||||
mappedFetchStyle,
|
||||
declaringType,
|
||||
propertyAccess
|
||||
|
@ -270,7 +270,7 @@ public class ToOneAttributeMapping
|
|||
);
|
||||
if ( bootValue instanceof ManyToOne ) {
|
||||
final ManyToOne manyToOne = (ManyToOne) bootValue;
|
||||
this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction();
|
||||
this.notFoundAction = determineNotFoundAction( ( (ManyToOne) bootValue ).getNotFoundAction(), entityMappingType );
|
||||
if ( manyToOne.isLogicalOneToOne() ) {
|
||||
cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ public class ToOneAttributeMapping
|
|||
else {
|
||||
this.bidirectionalAttributePath = SelectablePath.parse( oneToOne.getMappedByProperty() );
|
||||
}
|
||||
notFoundAction = null;
|
||||
notFoundAction = determineNotFoundAction( null, entityMappingType );
|
||||
isKeyTableNullable = isNullable();
|
||||
isOptional = !bootValue.isConstrained();
|
||||
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(
|
||||
String propertyPath,
|
||||
ManagedMappingType declaringType,
|
||||
|
@ -638,12 +648,18 @@ public class ToOneAttributeMapping
|
|||
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 ( ( (ManyToOne) bootValue ).getNotFoundAction() != null ) {
|
||||
return FetchTiming.IMMEDIATE;
|
||||
}
|
||||
}
|
||||
if ( entityMappingType.getEntityPersister().hasFilterForLoadByKey() ) {
|
||||
return FetchTiming.IMMEDIATE;
|
||||
}
|
||||
return mappedFetchTiming;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) {
|
||||
// todo (6.0) : anything to do here?
|
||||
|
|
|
@ -296,6 +296,10 @@ public interface CollectionPersister extends Restrictable {
|
|||
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) {
|
||||
throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" );
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -26,6 +25,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -536,6 +536,16 @@ public abstract class AbstractEntityPersister
|
|||
? MutableEntityEntryFactory.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()
|
||||
|
@ -804,16 +814,6 @@ public abstract class AbstractEntityPersister
|
|||
|
||||
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() );
|
||||
useShallowQueryCacheLayout = shouldUseShallowCacheLayout(
|
||||
persistentClass.getQueryCacheLayout(),
|
||||
|
@ -884,10 +884,10 @@ public abstract class AbstractEntityPersister
|
|||
final int batchSize = loadQueryInfluencers.effectiveBatchSize( this );
|
||||
return factory.getServiceRegistry()
|
||||
.requireService( BatchLoaderFactory.class )
|
||||
.createEntityBatchLoader( batchSize, this, factory );
|
||||
.createEntityBatchLoader( batchSize, this, loadQueryInfluencers );
|
||||
}
|
||||
else {
|
||||
return new SingleIdEntityLoaderStandardImpl<>( this, factory );
|
||||
return new SingleIdEntityLoaderStandardImpl<>( this, loadQueryInfluencers );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1255,6 +1255,18 @@ public abstract class AbstractEntityPersister
|
|||
return storeDiscriminatorInShallowQueryCacheLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFilterForLoadByKey() {
|
||||
if ( filterHelper != null ) {
|
||||
for ( String filterName : filterHelper.getFilterNames() ) {
|
||||
if ( factory.getFilterDefinition( filterName ).isApplyToLoadByKey() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
|
||||
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,
|
||||
Boolean readOnly,
|
||||
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;
|
||||
|
||||
protected SingleUniqueKeyEntityLoader<?> getUniqueKeyLoader(String attributeName) {
|
||||
protected SingleUniqueKeyEntityLoader<?> getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) {
|
||||
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;
|
||||
if ( uniqueKeyLoadersNew == null ) {
|
||||
uniqueKeyLoadersNew = new IdentityHashMap<>();
|
||||
uniqueKeyLoadersNew = new ConcurrentHashMap<>();
|
||||
existing = null;
|
||||
}
|
||||
else {
|
||||
|
@ -2673,7 +2694,7 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
else {
|
||||
final SingleUniqueKeyEntityLoader<?> loader =
|
||||
new SingleUniqueKeyEntityLoaderStandard<>( this, attribute );
|
||||
new SingleUniqueKeyEntityLoaderStandard<>( this, attribute, new LoadQueryInfluencers( factory ) );
|
||||
uniqueKeyLoadersNew.put( attribute, loader );
|
||||
return loader;
|
||||
}
|
||||
|
@ -3744,8 +3765,8 @@ public abstract class AbstractEntityPersister
|
|||
else {
|
||||
final LoadQueryInfluencers influencers = session.getLoadQueryInfluencers();
|
||||
// no subselect fetching for entities for now
|
||||
return isAffectedByInfluencers( influencers )
|
||||
? buildSingleIdEntityLoader( influencers )
|
||||
return isAffectedByInfluencersForLoadByKey( influencers )
|
||||
? buildSingleIdEntityLoader( influencers.copyForLoadByKey() )
|
||||
: getSingleIdLoader();
|
||||
}
|
||||
}
|
||||
|
@ -3863,6 +3884,30 @@ public abstract class AbstractEntityPersister
|
|||
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
|
||||
public boolean isSubclassPropertyNullable(int i) {
|
||||
return subclassPropertyNullabilityClosure[i];
|
||||
|
|
|
@ -1258,6 +1258,8 @@ public interface EntityPersister extends EntityMappingType, EntityMutationTarget
|
|||
@Incubating
|
||||
boolean storeDiscriminatorInShallowQueryCacheLayout();
|
||||
|
||||
boolean hasFilterForLoadByKey();
|
||||
|
||||
/**
|
||||
* The property name of the "special" identifier property in HQL
|
||||
*
|
||||
|
|
|
@ -14,13 +14,14 @@ import org.hibernate.sql.ast.SqlTreeCreationLogger;
|
|||
import org.hibernate.sql.ast.spi.SqlAstTreeHelper;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
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.DomainResultCreationState;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class TableGroupJoin implements TableJoin, DomainResultProducer {
|
||||
public class TableGroupJoin implements TableJoin, PredicateContainer, DomainResultProducer {
|
||||
private final NavigablePath navigablePath;
|
||||
private final TableGroup joinedGroup;
|
||||
|
||||
|
@ -78,6 +79,7 @@ public class TableGroupJoin implements TableJoin, DomainResultProducer {
|
|||
return predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPredicate(Predicate predicate) {
|
||||
this.predicate = SqlAstTreeHelper.combinePredicates( this.predicate, predicate );
|
||||
}
|
||||
|
|
|
@ -760,6 +760,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFilterForLoadByKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class LoadPlanBuilderTest {
|
|||
.getMappingMetamodel()
|
||||
.getEntityDescriptor( Message.class );
|
||||
|
||||
final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, sessionFactory );
|
||||
final SingleIdEntityLoaderStandardImpl<?> loader = new SingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( sessionFactory ) );
|
||||
|
||||
final SingleIdLoadPlan<?> loadPlan = loader.resolveLoadPlan(
|
||||
LockOptions.READ,
|
||||
|
@ -89,7 +89,7 @@ public class LoadPlanBuilderTest {
|
|||
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||
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 ) {
|
||||
@Override
|
||||
|
|
|
@ -739,6 +739,11 @@ public class PersisterClassProviderTest {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFilterForLoadByKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -896,6 +896,11 @@ public class CustomPersister implements EntityPersister {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFilterForLoadByKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<UniqueKeyEntry> uniqueKeyEntries() {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.function.Supplier;
|
|||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityGraph;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
|
@ -23,13 +24,18 @@ import jakarta.persistence.NoResultException;
|
|||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import org.hibernate.FetchNotFoundException;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
import org.hibernate.jpa.AvailableHints;
|
||||
import org.hibernate.metamodel.CollectionClassification;
|
||||
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 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.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
|
@ -58,47 +66,59 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
options.put( DEFAULT_LIST_SEMANTICS, CollectionClassification.BAG.name() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycle() {
|
||||
@Before
|
||||
public void setup() {
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
|
||||
//tag::pc-filter-persistence-example[]
|
||||
Client client = new Client()
|
||||
.setId(1L)
|
||||
.setName("John Doe")
|
||||
.setType(AccountType.DEBIT);
|
||||
.setId(1L)
|
||||
.setName("John Doe")
|
||||
.setType(AccountType.DEBIT);
|
||||
|
||||
Account account1;
|
||||
client.addAccount(
|
||||
account1 = new Account()
|
||||
.setId(1L)
|
||||
.setType(AccountType.CREDIT)
|
||||
.setAmount(5000d)
|
||||
.setRate(1.25 / 100)
|
||||
.setActive(true)
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId(1L)
|
||||
.setType(AccountType.CREDIT)
|
||||
.setAmount(5000d)
|
||||
.setRate(1.25 / 100)
|
||||
.setActive(true)
|
||||
);
|
||||
new Account()
|
||||
.setId(2L)
|
||||
.setType(AccountType.DEBIT)
|
||||
.setAmount(0d)
|
||||
.setRate(1.05 / 100)
|
||||
.setActive(false)
|
||||
.setParentAccount( account1 )
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId(2L)
|
||||
.setType(AccountType.DEBIT)
|
||||
.setAmount(0d)
|
||||
.setRate(1.05 / 100)
|
||||
.setActive(false)
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setType(AccountType.DEBIT)
|
||||
.setId(3L)
|
||||
.setAmount(250d)
|
||||
.setRate(1.05 / 100)
|
||||
.setActive(true)
|
||||
);
|
||||
new Account()
|
||||
.setType(AccountType.DEBIT)
|
||||
.setId(3L)
|
||||
.setAmount(250d)
|
||||
.setRate(1.05 / 100)
|
||||
.setActive(true)
|
||||
);
|
||||
|
||||
entityManager.persist(client);
|
||||
//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 -> {
|
||||
log.infof("Activate filter [%s]", "activeAccount");
|
||||
|
||||
|
@ -177,6 +197,33 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
//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 -> {
|
||||
log.infof("Activate filter [%s]", "minimumAmount");
|
||||
//tag::pc-filter-entity-example[]
|
||||
|
@ -220,28 +267,44 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
assertEquals(1, accounts.size());
|
||||
//end::pc-filter-entity-query-example[]
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-16830")
|
||||
public void testApplyToLoadByKeyAssociationFiltering() {
|
||||
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[]
|
||||
Account account = entityManager.find(Account.class, 2L);
|
||||
assertNotNull( account.getParentAccount() );
|
||||
});
|
||||
|
||||
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[]
|
||||
entityManager
|
||||
.unwrap(Session.class)
|
||||
.enableFilter("activeAccount")
|
||||
.setParameter("active", true);
|
||||
FetchNotFoundException exception = assertThrows(
|
||||
FetchNotFoundException.class,
|
||||
() -> entityManager.find( Account.class, 2L )
|
||||
);
|
||||
// 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);
|
||||
|
||||
assertEquals(2, client.getAccounts().size());
|
||||
//end::pc-filter-collection-query-example[]
|
||||
FetchNotFoundException exception = assertThrows(
|
||||
FetchNotFoundException.class,
|
||||
() -> entityManager.find(
|
||||
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",
|
||||
type=Double.class
|
||||
),
|
||||
applyToLoadById = true
|
||||
applyToLoadByKey = true
|
||||
)
|
||||
@Filter(
|
||||
name="minimumAmount",
|
||||
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 {
|
||||
|
||||
|
@ -361,6 +436,10 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
|
||||
//Getters and setters omitted for brevity
|
||||
//end::pc-filter-Account-example[]
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Account parentAccount;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -415,6 +494,14 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Account getParentAccount() {
|
||||
return parentAccount;
|
||||
}
|
||||
|
||||
public Account setParentAccount(Account parentAccount) {
|
||||
this.parentAccount = parentAccount;
|
||||
return this;
|
||||
}
|
||||
//tag::pc-filter-Account-example[]
|
||||
}
|
||||
//end::pc-filter-Account-example[]
|
||||
|
|
Loading…
Reference in New Issue