implement @Filter for HQL/Criteria
This commit is contained in:
parent
709e7d49b7
commit
686a519680
|
@ -18,7 +18,6 @@ import javax.persistence.OneToMany;
|
|||
import javax.persistence.OrderColumn;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.FilterJoinTable;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
@ -26,8 +25,6 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
@ -118,10 +115,6 @@ public class FilterJoinTableTest extends BaseEntityManagerFunctionalTestCase {
|
|||
type="int"
|
||||
)
|
||||
)
|
||||
@Filter(
|
||||
name="firstAccounts",
|
||||
condition="order_id <= :maxOrderId"
|
||||
)
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
|
|
|
@ -18,11 +18,13 @@ import javax.persistence.Id;
|
|||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
import org.hibernate.annotations.SqlFragmentAlias;
|
||||
import org.hibernate.annotations.Where;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
|
@ -56,7 +58,8 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
//tag::pc-filter-persistence-example[]
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
.setName( "John Doe" )
|
||||
.setType( AccountType.DEBIT );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
|
@ -186,7 +189,7 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
|
||||
Client client = entityManager.find( Client.class, 1L );
|
||||
|
||||
assertEquals( 2, client.getAccounts().size() );
|
||||
assertEquals( 1, client.getAccounts().size() );
|
||||
//end::pc-filter-collection-query-example[]
|
||||
} );
|
||||
}
|
||||
|
@ -198,6 +201,7 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
|
||||
//tag::pc-filter-Client-example[]
|
||||
@Entity(name = "Client")
|
||||
@Table(name = "client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
|
@ -205,13 +209,19 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
|
||||
private String name;
|
||||
|
||||
private AccountType type;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
condition="{a}.active_status = :active and {a}.type = {c}.type",
|
||||
aliases = {
|
||||
@SqlFragmentAlias( alias = "a", table= "account"),
|
||||
@SqlFragmentAlias( alias = "c", table= "client"),
|
||||
}
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>( );
|
||||
|
||||
|
@ -235,6 +245,15 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Client setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
@ -249,6 +268,7 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
|
||||
//tag::pc-filter-Account-example[]
|
||||
@Entity(name = "Account")
|
||||
@Table(name = "account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
|
|
|
@ -13,13 +13,22 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import org.hibernate.Filter;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.sql.Template;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
import static org.hibernate.internal.util.StringHelper.join;
|
||||
import static org.hibernate.internal.util.StringHelper.safeInterning;
|
||||
|
||||
/**
|
||||
|
@ -31,7 +40,7 @@ import static org.hibernate.internal.util.StringHelper.safeInterning;
|
|||
*/
|
||||
public class FilterHelper {
|
||||
|
||||
private static Pattern FILTER_PARAMETER_PATTERN = Pattern.compile( ":(\\w+)\\.(\\w+)" );
|
||||
private static final Pattern FILTER_PARAMETER_PATTERN = Pattern.compile( ":(\\w+)\\.(\\w+)" );
|
||||
|
||||
private final String[] filterNames;
|
||||
private final String[] filterConditions;
|
||||
|
@ -141,49 +150,30 @@ public class FilterHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static class TypedValue {
|
||||
private final Type type;
|
||||
private final Object value;
|
||||
|
||||
public TypedValue(Type type, Object value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
public static FilterPredicate createFilterPredicate(LoadQueryInfluencers loadQueryInfluencers, Joinable joinable, String alias) {
|
||||
if ( loadQueryInfluencers.hasEnabledFilters() ) {
|
||||
final String filterFragment;
|
||||
if ( joinable instanceof AbstractCollectionPersister && ( (AbstractCollectionPersister) joinable ).isManyToMany() ) {
|
||||
filterFragment = ( (AbstractCollectionPersister) joinable ).getManyToManyFilterFragment(
|
||||
alias,
|
||||
loadQueryInfluencers.getEnabledFilters()
|
||||
);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
else {
|
||||
filterFragment = joinable.filterFragment( alias, loadQueryInfluencers.getEnabledFilters() );
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
if ( ! StringHelper.isEmptyOrWhiteSpace( filterFragment ) ) {
|
||||
return doCreateFilterPredicate( filterFragment, loadQueryInfluencers.getEnabledFilters() );
|
||||
}
|
||||
}
|
||||
|
||||
public static class TransformResult {
|
||||
private final String transformedFilterFragment;
|
||||
private final List<TypedValue> parameters;
|
||||
|
||||
public TransformResult(
|
||||
String transformedFilterFragment,
|
||||
List<TypedValue> parameters) {
|
||||
this.transformedFilterFragment = transformedFilterFragment;
|
||||
this.parameters = parameters;
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getTransformedFilterFragment() {
|
||||
return transformedFilterFragment;
|
||||
}
|
||||
|
||||
public List<TypedValue> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public static TransformResult transformToPositionalParameters(String filterFragment, Map<String, Filter> enabledFilters) {
|
||||
private static FilterPredicate doCreateFilterPredicate(String filterFragment, Map<String, Filter> enabledFilters) {
|
||||
final Matcher matcher = FILTER_PARAMETER_PATTERN.matcher( filterFragment );
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
int pos = 0;
|
||||
final List<TypedValue> parameters = new ArrayList<>( matcher.groupCount() );
|
||||
final List<FilterJdbcParameter> parameters = new ArrayList<>( matcher.groupCount() );
|
||||
while( matcher.find() ) {
|
||||
sb.append( filterFragment, pos, matcher.start() );
|
||||
pos = matcher.end();
|
||||
|
@ -192,16 +182,19 @@ public class FilterHelper {
|
|||
final String parameterName = matcher.group( 2 );
|
||||
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
||||
if ( enabledFilter == null ) {
|
||||
throw new HibernateException( String.format( "unknown filter [%s]", filterName ) );
|
||||
throw new MappingException( String.format( "unknown filter [%s]", filterName ) );
|
||||
}
|
||||
final Type parameterType = enabledFilter.getFilterDefinition().getParameterType( parameterName );
|
||||
if ( ! (parameterType instanceof JdbcMapping ) ) {
|
||||
throw new MappingException( String.format( "parameter [%s] for filter [%s] is not of JdbcMapping type", parameterName, filterName ) );
|
||||
}
|
||||
final Object parameterValue = enabledFilter.getParameter( parameterName );
|
||||
if ( parameterValue == null ) {
|
||||
throw new HibernateException( String.format( "unknown parameter [%s] for filter [%s]", parameterName, filterName ) );
|
||||
throw new MappingException( String.format( "unknown parameter [%s] for filter [%s]", parameterName, filterName ) );
|
||||
}
|
||||
parameters.add( new TypedValue( parameterType, parameterValue ) );
|
||||
parameters.add( new FilterJdbcParameter( (JdbcMapping) parameterType, parameterValue ) );
|
||||
}
|
||||
sb.append( filterFragment, pos, filterFragment.length() );
|
||||
return new TransformResult( sb.toString(), parameters );
|
||||
return new FilterPredicate( sb.toString(), parameters );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.internal;
|
||||
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
public class FilterJdbcParameter {
|
||||
private final JdbcParameter parameter;
|
||||
private final JdbcMapping jdbcMapping;
|
||||
private final Object jdbcParameterValue;
|
||||
|
||||
public FilterJdbcParameter(JdbcMapping jdbcMapping, Object jdbcParameterValue) {
|
||||
this.parameter = new JdbcParameterImpl( jdbcMapping );
|
||||
this.jdbcMapping = jdbcMapping;
|
||||
this.jdbcParameterValue = jdbcParameterValue;
|
||||
}
|
||||
|
||||
public JdbcParameter getParameter() {
|
||||
return parameter;
|
||||
}
|
||||
|
||||
public JdbcParameterBinder getBinder() {
|
||||
return parameter.getParameterBinder();
|
||||
}
|
||||
|
||||
public JdbcParameterBinding getBinding() {
|
||||
return new JdbcParameterBinding() {
|
||||
@Override
|
||||
public JdbcMapping getBindType() {
|
||||
return jdbcMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBindValue() {
|
||||
return jdbcParameterValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -163,8 +163,7 @@ public class CollectionLoaderBatchKey implements CollectionLoader {
|
|||
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst );
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount * smallBatchLength );
|
||||
|
||||
sqlAst.getQuerySpec().bindFilterPredicateParameters( jdbcParameterBindings );
|
||||
jdbcSelect.registerFilterJdbcParameterBindings( jdbcParameterBindings );
|
||||
|
||||
final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator();
|
||||
|
||||
|
|
|
@ -99,6 +99,7 @@ public class CollectionLoaderSingleKey implements CollectionLoader {
|
|||
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst );
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount );
|
||||
jdbcSelect.registerFilterJdbcParameterBindings( jdbcParameterBindings );
|
||||
|
||||
final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator();
|
||||
|
||||
|
@ -127,8 +128,6 @@ public class CollectionLoaderSingleKey implements CollectionLoader {
|
|||
);
|
||||
assert !paramItr.hasNext();
|
||||
|
||||
sqlAst.getQuerySpec().bindFilterPredicateParameters( jdbcParameterBindings );
|
||||
|
||||
jdbcServices.getJdbcSelectExecutor().list(
|
||||
jdbcSelect,
|
||||
jdbcParameterBindings,
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SubselectFetch;
|
||||
import org.hibernate.internal.FilterHelper;
|
||||
import org.hibernate.internal.FilterHelper.TransformResult;
|
||||
import org.hibernate.loader.ast.spi.Loadable;
|
||||
import org.hibernate.loader.ast.spi.Loader;
|
||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||
|
@ -49,13 +48,11 @@ import org.hibernate.sql.ast.tree.expression.Expression;
|
|||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
|
||||
|
@ -231,7 +228,7 @@ public class LoaderSelectBuilder {
|
|||
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
|
||||
|
||||
if ( loadable instanceof PluralAttributeMapping ) {
|
||||
applyFiltering( rootQuerySpec, loadQueryInfluencers, (PluralAttributeMapping) loadable );
|
||||
applyFiltering( rootQuerySpec, rootTableGroup, (PluralAttributeMapping) loadable );
|
||||
applyOrdering( rootTableGroup, (PluralAttributeMapping) loadable );
|
||||
}
|
||||
|
||||
|
@ -382,62 +379,22 @@ public class LoaderSelectBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private void applyFiltering(
|
||||
QuerySpec querySpec,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
PluralAttributeMapping pluralAttributeMapping) {
|
||||
private void applyFiltering(QuerySpec querySpec, TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) {
|
||||
if ( loadQueryInfluencers.hasEnabledFilters() ) {
|
||||
final Joinable joinable = pluralAttributeMapping
|
||||
.getCollectionDescriptor()
|
||||
.getCollectionType()
|
||||
.getAssociatedJoinable( creationContext.getSessionFactory() );
|
||||
assert joinable instanceof AbstractCollectionPersister;
|
||||
final AbstractCollectionPersister collectionPersister = (AbstractCollectionPersister) joinable;
|
||||
querySpec.getFromClause().getRoots().forEach( tableGroup -> consumeTableAliasByTableExpression(
|
||||
tableGroup,
|
||||
joinable.getTableName(),
|
||||
alias -> {
|
||||
final boolean isManyToMany = collectionPersister.isManyToMany();
|
||||
String filterFragment;
|
||||
if ( isManyToMany ) {
|
||||
filterFragment = collectionPersister.getManyToManyFilterFragment(
|
||||
alias,
|
||||
loadQueryInfluencers.getEnabledFilters()
|
||||
);
|
||||
}
|
||||
else {
|
||||
filterFragment = collectionPersister.filterFragment(
|
||||
alias,
|
||||
loadQueryInfluencers.getEnabledFilters()
|
||||
);
|
||||
}
|
||||
final TransformResult transformResult = FilterHelper.transformToPositionalParameters(
|
||||
filterFragment, loadQueryInfluencers.getEnabledFilters()
|
||||
);
|
||||
filterFragment = transformResult.getTransformedFilterFragment();
|
||||
final FilterPredicate filterPredicate = new FilterPredicate(
|
||||
filterFragment, transformResult.getParameters()
|
||||
final String tableExpression = joinable.getTableName();
|
||||
final String tableAlias = tableGroup.resolveTableReference( tableExpression ).getIdentificationVariable();
|
||||
final Predicate filterPredicate = FilterHelper.createFilterPredicate(
|
||||
loadQueryInfluencers,
|
||||
joinable,
|
||||
tableAlias
|
||||
);
|
||||
if ( filterPredicate != null ) {
|
||||
querySpec.applyPredicate( filterPredicate );
|
||||
querySpec.addFilterPredicate( filterPredicate );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void consumeTableAliasByTableExpression(TableGroup tableGroup, String tableExpression, Consumer<String> aliasConsumer) {
|
||||
if ( tableExpression.equals( tableGroup.getPrimaryTableReference().getTableExpression() ) ) {
|
||||
aliasConsumer.accept( tableGroup.getPrimaryTableReference().getIdentificationVariable() );
|
||||
}
|
||||
else {
|
||||
for ( TableReferenceJoin referenceJoin : tableGroup.getTableReferenceJoins() ) {
|
||||
if ( tableExpression.equals( referenceJoin.getJoinedTableReference().getTableExpression() ) ) {
|
||||
aliasConsumer.accept( referenceJoin.getJoinedTableReference().getIdentificationVariable() );
|
||||
}
|
||||
}
|
||||
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {
|
||||
consumeTableAliasByTableExpression( tableGroupJoin.getJoinedGroup(), tableExpression, aliasConsumer );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -554,7 +511,11 @@ public class LoaderSelectBuilder {
|
|||
fetches.add( fetch );
|
||||
|
||||
if ( fetchable instanceof PluralAttributeMapping && fetchTiming == FetchTiming.IMMEDIATE && joined ) {
|
||||
applyFiltering( querySpec, loadQueryInfluencers, (PluralAttributeMapping) fetchable );
|
||||
applyFiltering(
|
||||
querySpec,
|
||||
creationState.getFromClauseAccess().getTableGroup( fetchablePath ),
|
||||
( (PluralAttributeMapping) fetchable )
|
||||
);
|
||||
applyOrdering(
|
||||
querySpec,
|
||||
fetchablePath,
|
||||
|
@ -630,7 +591,7 @@ public class LoaderSelectBuilder {
|
|||
sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup );
|
||||
|
||||
// NOTE : no need to check - we are explicitly processing a plural-attribute
|
||||
applyFiltering( rootQuerySpec, loadQueryInfluencers, (PluralAttributeMapping) loadable );
|
||||
applyFiltering( rootQuerySpec, rootTableGroup, attributeMapping );
|
||||
applyOrdering( rootTableGroup, attributeMapping );
|
||||
|
||||
// generate and apply the restriction
|
||||
|
|
|
@ -103,6 +103,7 @@ public class SingleIdLoadPlan<T> implements SingleEntityLoadPlan {
|
|||
assert jdbcParameters.size() % jdbcTypeCount == 0;
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcTypeCount );
|
||||
jdbcSelect.registerFilterJdbcParameterBindings( jdbcParameterBindings );
|
||||
|
||||
final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator();
|
||||
|
||||
|
@ -132,8 +133,6 @@ public class SingleIdLoadPlan<T> implements SingleEntityLoadPlan {
|
|||
);
|
||||
}
|
||||
|
||||
sqlAst.getQuerySpec().bindFilterPredicateParameters( jdbcParameterBindings );
|
||||
|
||||
final List list = JdbcSelectExecutorStandardImpl.INSTANCE.list(
|
||||
jdbcSelect,
|
||||
jdbcParameterBindings,
|
||||
|
|
|
@ -160,6 +160,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
sqmInterpretation.getTableGroupAccess()::findTableGroup,
|
||||
session
|
||||
);
|
||||
sqmInterpretation.getJdbcSelect().registerFilterJdbcParameterBindings( jdbcParameterBindings );
|
||||
|
||||
try {
|
||||
return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
|
||||
|
|
|
@ -45,6 +45,11 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
private static boolean isCacheable(QuerySqmImpl<?> query) {
|
||||
assert query.getQueryOptions().getAppliedGraph() != null;
|
||||
|
||||
if ( query.getSession().getLoadQueryInfluencers().hasEnabledFilters() ) {
|
||||
// At the moment we cannot cache query plan if there is filter enabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( query.getQueryOptions().getAppliedGraph().getSemantic() != null ) {
|
||||
// At the moment we cannot cache query plan if there is an
|
||||
// EntityGraph enabled.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.sql.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -19,15 +20,19 @@ import org.hibernate.NotYetImplementedFor6Exception;
|
|||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.engine.profile.FetchProfile;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.internal.FilterHelper;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.internal.util.collections.StandardStack;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.query.DynamicInstantiationNature;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
|
@ -57,6 +62,7 @@ import org.hibernate.sql.ast.tree.expression.Expression;
|
|||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
|
@ -89,6 +95,8 @@ public class StandardSqmSelectTranslator
|
|||
|
||||
private int fetchDepth;
|
||||
|
||||
private List<FilterPredicate> collectionFieldFilterPredicates;
|
||||
|
||||
public StandardSqmSelectTranslator(
|
||||
QueryOptions queryOptions,
|
||||
DomainParameterXref domainParameterXref,
|
||||
|
@ -187,6 +195,24 @@ public class StandardSqmSelectTranslator
|
|||
|
||||
@Override
|
||||
protected void postProcessQuerySpec(QuerySpec sqlQuerySpec) {
|
||||
final List<TableGroup> roots = sqlQuerySpec.getFromClause().getRoots();
|
||||
if ( roots != null && roots.size() == 1 ) {
|
||||
final TableGroup root = roots.get( 0 );
|
||||
final ModelPartContainer modelPartContainer = root.getModelPart();
|
||||
final EntityPersister entityPersister = modelPartContainer.findContainingEntityMapping().getEntityPersister();
|
||||
assert entityPersister instanceof AbstractEntityPersister;
|
||||
final String primaryTableAlias = root.getPrimaryTableReference().getIdentificationVariable();
|
||||
final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate(
|
||||
fetchInfluencers, (AbstractEntityPersister) entityPersister, primaryTableAlias
|
||||
);
|
||||
if ( filterPredicate != null ) {
|
||||
sqlQuerySpec.applyPredicate( filterPredicate );
|
||||
}
|
||||
if ( !CollectionHelper.isEmpty( collectionFieldFilterPredicates ) ) {
|
||||
collectionFieldFilterPredicates.forEach( sqlQuerySpec::applyPredicate );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent();
|
||||
if ( orderByFragmentConsumer != null ) {
|
||||
|
@ -384,10 +410,32 @@ public class StandardSqmSelectTranslator
|
|||
StandardSqmSelectTranslator.this
|
||||
);
|
||||
|
||||
if ( fetchable instanceof PluralAttributeMapping && fetch.getTiming() == FetchTiming.IMMEDIATE && joined ) {
|
||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
|
||||
|
||||
String tableAlias = alias;
|
||||
if ( tableAlias == null ) {
|
||||
tableAlias = getFromClauseAccess().getTableGroup( fetchablePath ).getPrimaryTableReference().getIdentificationVariable();
|
||||
}
|
||||
final Joinable joinable = pluralAttributeMapping
|
||||
.getCollectionDescriptor()
|
||||
.getCollectionType()
|
||||
.getAssociatedJoinable( getCreationContext().getSessionFactory() );
|
||||
final FilterPredicate collectionFieldFilterPredicate = FilterHelper.createFilterPredicate(
|
||||
fetchInfluencers,
|
||||
joinable,
|
||||
tableAlias
|
||||
);
|
||||
if ( collectionFieldFilterPredicate != null ) {
|
||||
if ( collectionFieldFilterPredicates == null ) {
|
||||
collectionFieldFilterPredicates = new ArrayList<>();
|
||||
}
|
||||
collectionFieldFilterPredicates.add( collectionFieldFilterPredicate );
|
||||
}
|
||||
|
||||
final OrderByFragmentConsumer orderByFragmentConsumer = orderByFragmentConsumerStack.getCurrent();
|
||||
if ( orderByFragmentConsumer != null ) {
|
||||
if ( fetchable instanceof PluralAttributeMapping && fetch.getTiming() == FetchTiming.IMMEDIATE ) {
|
||||
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable;
|
||||
|
||||
final TableGroup tableGroup = getFromClauseIndex().getTableGroup( fetchablePath );
|
||||
assert tableGroup.getModelPart() == pluralAttributeMapping;
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import org.hibernate.SortOrder;
|
|||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.FilterJdbcParameter;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
import org.hibernate.internal.util.collections.StandardStack;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
|
@ -70,11 +72,10 @@ import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate;
|
|||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.sql.exec.internal.AbstractJdbcParameter;
|
||||
import org.hibernate.sql.exec.internal.JdbcParametersImpl;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
|
||||
import org.hibernate.type.descriptor.sql.JdbcLiteralFormatter;
|
||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import static org.hibernate.query.TemporalUnit.NANOSECOND;
|
||||
|
@ -98,10 +99,12 @@ public abstract class AbstractSqlAstWalker
|
|||
|
||||
// In-flight state
|
||||
private final StringBuilder sqlBuffer = new StringBuilder();
|
||||
private final List<JdbcParameterBinder> parameterBinders = new ArrayList<>();
|
||||
|
||||
private final List<JdbcParameterBinder> parameterBinders = new ArrayList<>();
|
||||
private final JdbcParametersImpl jdbcParameters = new JdbcParametersImpl();
|
||||
|
||||
protected final List<FilterJdbcParameter> filterJdbcParameters = new ArrayList<>();
|
||||
|
||||
private final Stack<Clause> clauseStack = new StandardStack<>();
|
||||
|
||||
private final Dialect dialect;
|
||||
|
@ -1034,12 +1037,12 @@ public abstract class AbstractSqlAstWalker
|
|||
|
||||
@Override
|
||||
public void visitFilterPredicate(FilterPredicate filterPredicate) {
|
||||
if ( filterPredicate.getFilterFragment() != null ) {
|
||||
assert StringHelper.isNotEmpty( filterPredicate.getFilterFragment() );
|
||||
appendSql( filterPredicate.getFilterFragment() );
|
||||
for (JdbcParameter jdbcParameter : filterPredicate.getJdbcParameters()) {
|
||||
parameterBinders.add( (AbstractJdbcParameter) jdbcParameter );
|
||||
jdbcParameters.addParameter( jdbcParameter );
|
||||
}
|
||||
for ( FilterJdbcParameter filterJdbcParameter : filterPredicate.getFilterJdbcParameters() ) {
|
||||
parameterBinders.add( filterJdbcParameter.getBinder() );
|
||||
jdbcParameters.addParameter( filterJdbcParameter.getParameter() );
|
||||
filterJdbcParameters.add( filterJdbcParameter );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ public class StandardSqlAstSelectTranslator
|
|||
querySpec.getSelectClause().getSqlSelections(),
|
||||
Collections.emptyList()
|
||||
),
|
||||
getAffectedTableNames()
|
||||
getAffectedTableNames(),
|
||||
filterJdbcParameters
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -93,7 +94,8 @@ public class StandardSqlAstSelectTranslator
|
|||
sqlAstSelect.getQuerySpec().getSelectClause().getSqlSelections(),
|
||||
sqlAstSelect.getDomainResultDescriptors()
|
||||
),
|
||||
getAffectedTableNames()
|
||||
getAffectedTableNames(),
|
||||
filterJdbcParameters
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.tree.predicate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.internal.FilterHelper;
|
||||
import org.hibernate.internal.FilterJdbcParameter;
|
||||
import org.hibernate.sql.ast.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
|
||||
|
||||
/**
|
||||
* Represents a filter applied to an entity/collection.
|
||||
|
@ -20,19 +17,15 @@ import org.hibernate.sql.exec.internal.JdbcParameterImpl;
|
|||
* Note, we do not attempt to parse the filter
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
public class FilterPredicate implements Predicate {
|
||||
private final String filterFragment;
|
||||
private final List<JdbcParameter> jdbcParameters;
|
||||
private final List<FilterHelper.TypedValue> jdbcParameterTypedValues;
|
||||
private final List<FilterJdbcParameter> filterJdbcParameters;
|
||||
|
||||
public FilterPredicate(String filterFragment, List<FilterHelper.TypedValue> jdbcParameterTypedValues) {
|
||||
public FilterPredicate(String filterFragment, List<FilterJdbcParameter> filterJdbcParameters) {
|
||||
this.filterFragment = filterFragment;
|
||||
jdbcParameters = new ArrayList<>( jdbcParameterTypedValues.size() );
|
||||
this.jdbcParameterTypedValues = jdbcParameterTypedValues;
|
||||
for (int i = 0; i < jdbcParameterTypedValues.size(); i++) {
|
||||
jdbcParameters.add( new JdbcParameterImpl( null ) );
|
||||
}
|
||||
this.filterJdbcParameters = filterJdbcParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,11 +42,7 @@ public class FilterPredicate implements Predicate {
|
|||
return filterFragment;
|
||||
}
|
||||
|
||||
public List<JdbcParameter> getJdbcParameters() {
|
||||
return jdbcParameters;
|
||||
}
|
||||
|
||||
public List<FilterHelper.TypedValue> getJdbcParameterTypedValues() {
|
||||
return jdbcParameterTypedValues;
|
||||
public List<FilterJdbcParameter> getFilterJdbcParameters() {
|
||||
return filterJdbcParameters;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.internal.FilterHelper;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressable;
|
||||
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
|
||||
import org.hibernate.sql.ast.SqlAstWalker;
|
||||
|
@ -22,13 +19,9 @@ import org.hibernate.sql.ast.spi.SqlSelection;
|
|||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.cte.CteConsumer;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.from.FromClause;
|
||||
import org.hibernate.sql.ast.tree.predicate.FilterPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.PredicateContainer;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||
|
@ -45,7 +38,6 @@ public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, Ct
|
|||
private final SelectClause selectClause = new SelectClause();
|
||||
|
||||
private Predicate whereClauseRestrictions;
|
||||
private List<FilterPredicate> filterPredicates;
|
||||
private List<SortSpecification> sortSpecifications;
|
||||
private Expression limitClauseExpression;
|
||||
private Expression offsetClauseExpression;
|
||||
|
@ -85,13 +77,6 @@ public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, Ct
|
|||
this.whereClauseRestrictions = SqlAstTreeHelper.combinePredicates( this.whereClauseRestrictions, predicate );
|
||||
}
|
||||
|
||||
public void addFilterPredicate(FilterPredicate filterPredicate) {
|
||||
if ( filterPredicates == null ) {
|
||||
filterPredicates = new ArrayList<>();
|
||||
}
|
||||
filterPredicates.add( filterPredicate );
|
||||
}
|
||||
|
||||
public List<SortSpecification> getSortSpecifications() {
|
||||
return sortSpecifications;
|
||||
}
|
||||
|
@ -170,32 +155,4 @@ public class QuerySpec implements SqlAstNode, PredicateContainer, Expression, Ct
|
|||
descriptor
|
||||
);
|
||||
}
|
||||
|
||||
public void bindFilterPredicateParameters(JdbcParameterBindings jdbcParameterBindings) {
|
||||
if ( filterPredicates != null && !filterPredicates.isEmpty() ) {
|
||||
for ( FilterPredicate filterPredicate : filterPredicates ) {
|
||||
for ( int i = 0; i < filterPredicate.getJdbcParameters().size(); i++ ) {
|
||||
final JdbcParameter parameter = filterPredicate.getJdbcParameters().get( i );
|
||||
final FilterHelper.TypedValue parameterTypedValue = filterPredicate.getJdbcParameterTypedValues().get( i );
|
||||
if ( !(parameterTypedValue.getType() instanceof JdbcMapping ) ) {
|
||||
throw new HibernateException( String.format( "Filter parameter type [%s] did not implement JdbcMapping", parameterTypedValue.getType() ) );
|
||||
}
|
||||
jdbcParameterBindings.addBinding(
|
||||
parameter,
|
||||
new JdbcParameterBinding() {
|
||||
@Override
|
||||
public JdbcMapping getBindType() {
|
||||
return (JdbcMapping) parameterTypedValue.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBindValue() {
|
||||
return parameterTypedValue.getValue();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ package org.hibernate.sql.exec.spi;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.internal.FilterJdbcParameter;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
|
||||
|
||||
/**
|
||||
|
@ -21,16 +23,19 @@ public class JdbcSelect implements JdbcOperation {
|
|||
private final List<JdbcParameterBinder> parameterBinders;
|
||||
private final JdbcValuesMappingProducer jdbcValuesMappingProducer;
|
||||
private final Set<String> affectedTableNames;
|
||||
private final List<FilterJdbcParameter> filterJdbcParameters;
|
||||
|
||||
public JdbcSelect(
|
||||
String sql,
|
||||
List<JdbcParameterBinder> parameterBinders,
|
||||
JdbcValuesMappingProducer jdbcValuesMappingProducer,
|
||||
Set<String> affectedTableNames) {
|
||||
Set<String> affectedTableNames,
|
||||
List<FilterJdbcParameter> filterJdbcParameters) {
|
||||
this.sql = sql;
|
||||
this.parameterBinders = parameterBinders;
|
||||
this.jdbcValuesMappingProducer = jdbcValuesMappingProducer;
|
||||
this.affectedTableNames = affectedTableNames;
|
||||
this.filterJdbcParameters = filterJdbcParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,4 +56,12 @@ public class JdbcSelect implements JdbcOperation {
|
|||
public JdbcValuesMappingProducer getJdbcValuesMappingProducer() {
|
||||
return jdbcValuesMappingProducer;
|
||||
}
|
||||
|
||||
public void registerFilterJdbcParameterBindings(JdbcParameterBindings jdbcParameterBindings) {
|
||||
if ( CollectionHelper.isNotEmpty( filterJdbcParameters ) ) {
|
||||
for ( FilterJdbcParameter filterJdbcParameter : filterJdbcParameters ) {
|
||||
jdbcParameterBindings.addBinding( filterJdbcParameter.getParameter(), filterJdbcParameter.getBinding() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterBasicsTests.Client.class,
|
||||
FilterBasicsTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterBasicsTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnEntity(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
final CriteriaBuilder criteriaBuilder = scope.getSessionFactory().getCriteriaBuilder();
|
||||
|
||||
final CriteriaQuery<Account> criteriaQuery1 = createCriteriaQuery( criteriaBuilder, Account.class, "id", 1L );
|
||||
Account account1 = session.createQuery( criteriaQuery1 ).uniqueResult();
|
||||
assertThat( account1, notNullValue() );
|
||||
|
||||
final CriteriaQuery<Account> criteriaQuery2 = createCriteriaQuery( criteriaBuilder, Account.class, "id", 2L );
|
||||
Account account2 = session.createQuery( criteriaQuery2 ).uniqueResult();
|
||||
assertThat( account2, enableFilter ? nullValue() : notNullValue() );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnCollectionField(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
|
||||
final CriteriaBuilder criteriaBuilder = scope.getSessionFactory().getCriteriaBuilder();
|
||||
final CriteriaQuery<Client> criteriaQuery = createCriteriaQuery( criteriaBuilder, Client.class, "id", 1L );
|
||||
final Client client = session.createQuery(criteriaQuery).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> CriteriaQuery<T> createCriteriaQuery(CriteriaBuilder criteriaBuilder, Class<T> entityClass, String idFieldName, Object idValue) {
|
||||
final CriteriaQuery<T> criteria = criteriaBuilder.createQuery( entityClass );
|
||||
Root<T> root = criteria.from( entityClass );
|
||||
criteria.select( root );
|
||||
criteria.where( criteriaBuilder.equal( root.get( idFieldName ), criteriaBuilder.literal( idValue ) ) );
|
||||
return criteria;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OrderColumn;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.FilterJoinTable;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterJoinTableTests.Client.class,
|
||||
FilterJoinTableTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterJoinTableTests {
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 2L )
|
||||
.setType( AccountType.DEBIT )
|
||||
.setAmount( 0d )
|
||||
.setRate( 1.05 / 100 )
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setType( AccountType.DEBIT )
|
||||
.setId( 3L )
|
||||
.setAmount( 250d )
|
||||
.setRate( 1.05 / 100 )
|
||||
);
|
||||
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadFilterOnCollectionField(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.enableFilter( "firstAccounts" ).setParameter( "maxOrderId", 1);
|
||||
|
||||
final CriteriaBuilder criteriaBuilder = scope.getSessionFactory().getCriteriaBuilder();
|
||||
final CriteriaQuery<Client> criteriaQuery = createCriteriaQuery( criteriaBuilder, Client.class, "id", 1L );
|
||||
final Client client = session.createQuery( criteriaQuery ).uniqueResult();
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L ) ) ) );
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
@FilterDef(
|
||||
name="firstAccounts",
|
||||
parameters=@ParamDef(
|
||||
name="maxOrderId",
|
||||
type="int"
|
||||
)
|
||||
)
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
@JoinTable
|
||||
@OrderColumn(name = "order_id")
|
||||
@FilterJoinTable(
|
||||
name="firstAccounts",
|
||||
condition="order_id <= :maxOrderId"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> CriteriaQuery<T> createCriteriaQuery(CriteriaBuilder criteriaBuilder, Class<T> entityClass, String idFieldName, Object idValue) {
|
||||
final CriteriaQuery<T> criteria = criteriaBuilder.createQuery( entityClass );
|
||||
Root<T> root = criteria.from( entityClass );
|
||||
criteria.select( root );
|
||||
criteria.where( criteriaBuilder.equal( root.get( idFieldName ), criteriaBuilder.literal( idValue ) ) );
|
||||
return criteria;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterOnJoinFetchedCollectionTests.Client.class,
|
||||
FilterOnJoinFetchedCollectionTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterOnJoinFetchedCollectionTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testJoinFetchedCollectionField(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
final CriteriaBuilder criteriaBuilder = scope.getSessionFactory().getCriteriaBuilder();
|
||||
final CriteriaQuery<Client> criteriaQuery = createCriteriaQuery( criteriaBuilder, Client.class, "id", 1L );
|
||||
final Client client = session.createQuery( criteriaQuery ).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> CriteriaQuery<T> createCriteriaQuery(CriteriaBuilder criteriaBuilder, Class<T> entityClass, String idFieldName, Object idValue) {
|
||||
final CriteriaQuery<T> criteria = criteriaBuilder.createQuery( entityClass );
|
||||
Root<T> root = criteria.from( entityClass );
|
||||
criteria.select( root );
|
||||
criteria.where( criteriaBuilder.equal( root.get( idFieldName ), criteriaBuilder.literal( idValue ) ) );
|
||||
return criteria;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.criteria.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
import org.hibernate.annotations.SqlFragmentAlias;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterWithSqlFragmentAliasTests.Client.class,
|
||||
FilterWithSqlFragmentAliasTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterWithSqlFragmentAliasTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnCollectionField(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
|
||||
final CriteriaBuilder criteriaBuilder = scope.getSessionFactory().getCriteriaBuilder();
|
||||
final CriteriaQuery<Client> criteriaQuery = createCriteriaQuery( criteriaBuilder, Client.class, "id", 1L );
|
||||
final Client client = session.createQuery( criteriaQuery ).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
@Table(name = "client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private AccountType type;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="{a}.active_status = :active",
|
||||
aliases = {
|
||||
@SqlFragmentAlias( alias = "a", table= "account")
|
||||
}
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(AccountType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@Table(name = "account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> CriteriaQuery<T> createCriteriaQuery(CriteriaBuilder criteriaBuilder, Class<T> entityClass, String idFieldName, Object idValue) {
|
||||
final CriteriaQuery<T> criteria = criteriaBuilder.createQuery( entityClass );
|
||||
Root<T> root = criteria.from( entityClass );
|
||||
criteria.select( root );
|
||||
criteria.where( criteriaBuilder.equal( root.get( idFieldName ), criteriaBuilder.literal( idValue ) ) );
|
||||
return criteria;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.hql.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterBasicsTests.Client.class,
|
||||
FilterBasicsTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterBasicsTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnEntity(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
final String hqlString = "select a from Account a where a.id = :id";
|
||||
final Account account1 = session.createQuery( hqlString, Account.class )
|
||||
.setParameter( "id", 1L ).uniqueResult();
|
||||
final Account account2 = session.createQuery( hqlString, Account.class )
|
||||
.setParameter( "id", 2L ).uniqueResult();
|
||||
assertThat( account1, notNullValue() );
|
||||
assertThat( account2, enableFilter ? nullValue() : notNullValue() );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnCollectionField(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
Client client = session.createQuery( "select c from Client c where c.id = :id", Client.class )
|
||||
.setParameter( "id", 1L ).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map(Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map(Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.hql.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OrderColumn;
|
||||
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.FilterJoinTable;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterJoinTableTests.Client.class,
|
||||
FilterJoinTableTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterJoinTableTests {
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 2L )
|
||||
.setType( AccountType.DEBIT )
|
||||
.setAmount( 0d )
|
||||
.setRate( 1.05 / 100 )
|
||||
);
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setType( AccountType.DEBIT )
|
||||
.setId( 3L )
|
||||
.setAmount( 250d )
|
||||
.setRate( 1.05 / 100 )
|
||||
);
|
||||
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFilterJoinableOnCollectionField(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.enableFilter( "firstAccounts" ).setParameter( "maxOrderId", 1 );
|
||||
final Client client = session.createQuery( "select c from Client c where c.id = :id", Client.class )
|
||||
.setParameter( "id", 1L ).uniqueResult();
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L ) ) ) );
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
@FilterDef(
|
||||
name="firstAccounts",
|
||||
parameters=@ParamDef(
|
||||
name="maxOrderId",
|
||||
type="int"
|
||||
)
|
||||
)
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
@JoinTable
|
||||
@OrderColumn(name = "order_id")
|
||||
@FilterJoinTable(
|
||||
name="firstAccounts",
|
||||
condition="order_id <= :maxOrderId"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.hql.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterOnJoinFetchedCollectionTests.Client.class,
|
||||
FilterOnJoinFetchedCollectionTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterOnJoinFetchedCollectionTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testLoadFilterOnCollectionField(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
final Client client = session.createQuery( "select c from Client c join fetch c.accounts where c.id = :id", Client.class )
|
||||
.setParameter( "id", 1L ).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.query.hql.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
import org.hibernate.annotations.SqlFragmentAlias;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
FilterWithSqlFragmentAliasTests.Client.class,
|
||||
FilterWithSqlFragmentAliasTests.Account.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
public class FilterWithSqlFragmentAliasTests implements SessionFactoryScopeAware {
|
||||
|
||||
private SessionFactoryScope scope;
|
||||
|
||||
@Override
|
||||
public void injectSessionFactoryScope(SessionFactoryScope scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
// ensure query plan cache won't interfere
|
||||
scope.getSessionFactory().getQueryEngine().getInterpretationCache().close();
|
||||
|
||||
Client client = new Client()
|
||||
.setId( 1L )
|
||||
.setName( "John Doe" )
|
||||
.setType( AccountType.DEBIT );
|
||||
|
||||
client.addAccount(
|
||||
new Account()
|
||||
.setId( 1L )
|
||||
.setType( AccountType.CREDIT )
|
||||
.setAmount( 5000d )
|
||||
.setRate( 1.25 / 100 )
|
||||
.setActive( true )
|
||||
);
|
||||
|
||||
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 )
|
||||
);
|
||||
session.persist( client );
|
||||
} );
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = { "true", "false" } )
|
||||
void testSqlFragmentAlias(boolean enableFilter) {
|
||||
scope.inTransaction( session -> {
|
||||
if ( enableFilter ) {
|
||||
session.enableFilter( "activeAccount" ).setParameter( "active", true );
|
||||
}
|
||||
Client client = session.createQuery( "select c from Client c where c.id = :id", Client.class )
|
||||
.setParameter( "id", 1L ).uniqueResult();
|
||||
|
||||
if ( enableFilter ) {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 3L ) ) ) );
|
||||
}
|
||||
else {
|
||||
assertThat( client.getAccounts().stream().map( Account::getId ).collect( Collectors.toSet() ),
|
||||
equalTo( new HashSet<>( Arrays.asList( 1L, 2L, 3L ) ) ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createQuery( "delete from Account" ).executeUpdate();
|
||||
session.createQuery( "delete from Client" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
public enum AccountType {
|
||||
DEBIT,
|
||||
CREDIT
|
||||
}
|
||||
|
||||
@Entity(name = "Client")
|
||||
@Table(name = "client")
|
||||
public static class Client {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private AccountType type;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "client",
|
||||
cascade = CascadeType.ALL
|
||||
)
|
||||
@Filter(
|
||||
name="activeAccount",
|
||||
condition="{a}.active_status = :active",
|
||||
aliases = {
|
||||
@SqlFragmentAlias( alias = "a", table= "account")
|
||||
}
|
||||
)
|
||||
private List<Account> accounts = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Client setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Client setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Client setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void addAccount(Account account) {
|
||||
account.setClient( this );
|
||||
this.accounts.add( account );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Account")
|
||||
@Table(name = "account")
|
||||
@FilterDef(
|
||||
name="activeAccount",
|
||||
parameters = @ParamDef(
|
||||
name="active",
|
||||
type="boolean"
|
||||
)
|
||||
)
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
private Client client;
|
||||
|
||||
@Column(name = "account_type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AccountType type;
|
||||
|
||||
private Double amount;
|
||||
|
||||
private Double rate;
|
||||
|
||||
@Column(name = "active_status")
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Account setId(Long id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Account setClient(Client client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Account setType(AccountType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Account setAmount(Double amount) {
|
||||
this.amount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public Account setRate(Double rate) {
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public Account setActive(boolean active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue