HHH-16830: apply filters to find() method
This commit is contained in:
parent
84f2f3535f
commit
e721a37691
|
@ -558,9 +558,11 @@ include::{example-dir-pc}/FilterTest.java[tags=pc-filter-resolver-Account-exampl
|
|||
|
||||
[IMPORTANT]
|
||||
====
|
||||
Filters apply to entity queries, but not to direct fetching.
|
||||
Filters apply to entity queries, but not to direct fetching, unless otherwise configured using the `applyToLoadByKey` flag
|
||||
on the `@FilterDef`, that should be set to `true` in order to activate the filter with direct fetching.
|
||||
|
||||
Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.
|
||||
Therefore, in the following example, the `activeAccount` filter is not taken into consideration when fetching an entity from the Persistence Context.
|
||||
On the other hand, the `minimumAmount` filter is taken into consideration, because its `applyToLoadByKey` flag is set to `true`.
|
||||
|
||||
[[pc-filter-entity-example]]
|
||||
.Fetching entities mapped with `@Filter`
|
||||
|
@ -574,7 +576,13 @@ include::{example-dir-pc}/FilterTest.java[tags=pc-filter-entity-example]
|
|||
include::{extrasdir}/pc-filter-entity-example.sql[]
|
||||
----
|
||||
|
||||
As you can see from the example above, contrary to an entity query, the filter does not prevent the entity from being loaded.
|
||||
[source, SQL, indent=0]
|
||||
----
|
||||
include::{extrasdir}/pc-filter-entity-find-example.sql[]
|
||||
----
|
||||
|
||||
As you can see from the example above, contrary to an entity query, the `activeAccount` filter does not prevent the entity from being loaded,
|
||||
but the `minimumAmount` filter limits the results to the ones with an amount that is greater than the specified one.
|
||||
====
|
||||
|
||||
Just like with entity queries, collections can be filtered as well, but only if the filter is enabled on the currently running Hibernate `Session`,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
SELECT
|
||||
a.id as id1_0_0_,
|
||||
a.active_status as active2_0_0_,
|
||||
a.amount as amount3_0_0_,
|
||||
a.client_id as client_i6_0_0_,
|
||||
a.rate as rate4_0_0_,
|
||||
a.account_type as account_5_0_0_,
|
||||
c.id as id1_1_1_,
|
||||
c.name as name2_1_1_
|
||||
FROM
|
||||
Account a
|
||||
WHERE
|
||||
a.id = 1
|
||||
AND a.amount > 9000
|
|
@ -95,4 +95,12 @@ public interface Filter {
|
|||
* @return The flag value
|
||||
*/
|
||||
boolean isAutoEnabled();
|
||||
|
||||
/**
|
||||
* Get the associated {@link FilterDefinition applyToLoadByKey} of this
|
||||
* named filter.
|
||||
*
|
||||
* @return The flag value
|
||||
*/
|
||||
boolean isApplyToLoadByKey();
|
||||
}
|
||||
|
|
|
@ -95,4 +95,13 @@ public @interface FilterDef {
|
|||
* The flag used to auto-enable the filter on the session.
|
||||
*/
|
||||
boolean autoEnabled() default false;
|
||||
|
||||
/**
|
||||
* The flag used to decide if the filter will
|
||||
* be applied on direct fetches or not.
|
||||
* <p>
|
||||
* If the flag is true, the filter will be
|
||||
* applied on direct fetches, such as findById().
|
||||
*/
|
||||
boolean applyToLoadByKey() default false;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
|
|||
"",
|
||||
singletonMap( PARAMETER_NAME, tenantIdType ),
|
||||
Collections.emptyMap(),
|
||||
false
|
||||
true,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,8 @@ class FilterDefBinder {
|
|||
filterDef.defaultCondition(),
|
||||
explicitParamJaMappings,
|
||||
parameterResolvers,
|
||||
filterDef.autoEnabled()
|
||||
filterDef.autoEnabled(),
|
||||
filterDef.applyToLoadByKey()
|
||||
);
|
||||
LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() );
|
||||
context.getMetadataCollector().addFilterDefinition( filterDefinition );
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.boot.model.source.internal.hbm;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ public class FilterDefinition implements Serializable {
|
|||
private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>();
|
||||
private final Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap = new HashMap<>();
|
||||
private final boolean autoEnabled;
|
||||
private final boolean applyToLoadByKey;
|
||||
|
||||
/**
|
||||
* Construct a new FilterDefinition instance.
|
||||
|
@ -44,17 +45,18 @@ public class FilterDefinition implements Serializable {
|
|||
* @param name The name of the filter for which this configuration is in effect.
|
||||
*/
|
||||
public FilterDefinition(String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings) {
|
||||
this( name, defaultCondition, explicitParamJaMappings, Collections.emptyMap(), false);
|
||||
this( name, defaultCondition, explicitParamJaMappings, Collections.emptyMap(), false, false);
|
||||
}
|
||||
|
||||
public FilterDefinition(
|
||||
String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings,
|
||||
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap, boolean autoEnabled) {
|
||||
Map<String, ManagedBean<? extends Supplier<?>>> parameterResolverMap, boolean autoEnabled, boolean applyToLoadByKey) {
|
||||
this.filterName = name;
|
||||
this.defaultFilterCondition = defaultCondition;
|
||||
if ( explicitParamJaMappings != null ) {
|
||||
this.explicitParamJaMappings.putAll( explicitParamJaMappings );
|
||||
}
|
||||
this.applyToLoadByKey = applyToLoadByKey;
|
||||
if ( parameterResolverMap != null ) {
|
||||
this.parameterResolverMap.putAll( parameterResolverMap );
|
||||
}
|
||||
|
@ -101,6 +103,16 @@ public class FilterDefinition implements Serializable {
|
|||
return defaultFilterCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flag that defines if the filter should be applied
|
||||
* on direct fetches or not.
|
||||
*
|
||||
* @return The flag value.
|
||||
*/
|
||||
public boolean isApplyToLoadByKey() {
|
||||
return applyToLoadByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before binding a JDBC parameter
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.HashSet;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.hibernate.Filter;
|
||||
import org.hibernate.Internal;
|
||||
|
@ -168,6 +169,20 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of enabled filters that have the applyToLoadByKey
|
||||
* flag set to true
|
||||
* @return a Map of enabled filters that have the applyToLoadByKey
|
||||
* flag set to true
|
||||
*/
|
||||
public Map<String, Filter> getEnabledFiltersForFind() {
|
||||
return getEnabledFilters()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(f -> f.getValue().isApplyToLoadByKey())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Set of enabled filter names.
|
||||
* @return an unmodifiable Set of enabled filter names.
|
||||
|
|
|
@ -32,6 +32,7 @@ public class FilterImpl implements Filter, Serializable {
|
|||
private final String filterName;
|
||||
private final Map<String,Object> parameters = new HashMap<>();
|
||||
private final boolean autoEnabled;
|
||||
private final boolean applyToLoadByKey;
|
||||
|
||||
void afterDeserialize(SessionFactoryImplementor factory) {
|
||||
definition = factory.getFilterDefinition( filterName );
|
||||
|
@ -47,6 +48,7 @@ public class FilterImpl implements Filter, Serializable {
|
|||
this.definition = configuration;
|
||||
filterName = definition.getFilterName();
|
||||
this.autoEnabled = definition.isAutoEnabled();
|
||||
this.applyToLoadByKey = definition.isApplyToLoadByKey();
|
||||
}
|
||||
|
||||
public FilterDefinition getFilterDefinition() {
|
||||
|
@ -71,6 +73,17 @@ public class FilterImpl implements Filter, Serializable {
|
|||
return autoEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a flag that defines if the filter should be applied
|
||||
* on direct fetches or not.
|
||||
*
|
||||
* @return The flag value.
|
||||
*/
|
||||
public boolean isApplyToLoadByKey() {
|
||||
return applyToLoadByKey;
|
||||
}
|
||||
|
||||
public Map<String,?> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
package org.hibernate.loader.ast.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -717,8 +716,8 @@ public class LoaderSelectBuilder {
|
|||
querySpec::applyPredicate,
|
||||
tableGroup,
|
||||
true,
|
||||
// HHH-16179 Session.find should not apply filters
|
||||
Collections.emptyMap(),//loadQueryInfluencers.getEnabledFilters(),
|
||||
// HHH-16830 Session.find should apply filters only if specified on the filter definition
|
||||
loadQueryInfluencers.getEnabledFiltersForFind(),
|
||||
null,
|
||||
astCreationState
|
||||
);
|
||||
|
|
|
@ -2281,6 +2281,7 @@
|
|||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="condition" type="xsd:string" minOccurs="0"/>
|
||||
<xsd:element name="apply-to-load-by-key" type="xsd:boolean" default="false"/>
|
||||
</xsd:sequence>
|
||||
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
|
|
|
@ -177,6 +177,50 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
//end::pc-filter-entity-query-example[]
|
||||
});
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
log.infof("Activate filter [%s]", "minimumAmount");
|
||||
//tag::pc-filter-entity-example[]
|
||||
entityManager
|
||||
.unwrap(Session.class)
|
||||
.enableFilter("minimumAmount")
|
||||
.setParameter("amount", 9000d);
|
||||
|
||||
Account account = entityManager.find(Account.class, 1L);
|
||||
|
||||
assertNull( account );
|
||||
//end::pc-filter-entity-example[]
|
||||
});
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
log.infof("Activate filter [%s]", "minimumAmount");
|
||||
//tag::pc-filter-entity-example[]
|
||||
entityManager
|
||||
.unwrap(Session.class)
|
||||
.enableFilter("minimumAmount")
|
||||
.setParameter("amount", 100d);
|
||||
|
||||
Account account = entityManager.find(Account.class, 1L);
|
||||
|
||||
assertNotNull( account );
|
||||
//end::pc-filter-entity-example[]
|
||||
});
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
log.infof("Activate filter [%s]", "minimumAmount");
|
||||
//tag::pc-filter-entity-query-example[]
|
||||
entityManager
|
||||
.unwrap(Session.class)
|
||||
.enableFilter("minimumAmount")
|
||||
.setParameter("amount", 500d);
|
||||
|
||||
List<Account> accounts = entityManager.createQuery(
|
||||
"select a from Account a", Account.class)
|
||||
.getResultList();
|
||||
|
||||
assertEquals(1, accounts.size());
|
||||
//end::pc-filter-entity-query-example[]
|
||||
});
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::pc-no-filter-collection-query-example[]
|
||||
Client client = entityManager.find(Client.class, 1L);
|
||||
|
@ -283,6 +327,19 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
|||
name="activeAccount",
|
||||
condition="active_status = :active"
|
||||
)
|
||||
@FilterDef(
|
||||
name="minimumAmount",
|
||||
parameters = @ParamDef(
|
||||
name="amount",
|
||||
type=Double.class
|
||||
),
|
||||
applyToLoadByKey = true
|
||||
)
|
||||
@Filter(
|
||||
name="minimumAmount",
|
||||
condition="amount > :amount"
|
||||
)
|
||||
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
|
|
|
@ -40,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION;
|
||||
import static org.hibernate.internal.util.collections.CollectionHelper.toMap;
|
||||
import static org.hibernate.jpa.HibernateHints.HINT_TENANT_ID;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -110,7 +111,8 @@ public class TenantIdTest implements SessionFactoryProducer {
|
|||
|
||||
currentTenant = "yours";
|
||||
scope.inTransaction( session -> {
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
//HHH-16830 Sessions applies tenantId filter on find()
|
||||
assertNull( session.find(Account.class, acc.id) );
|
||||
assertEquals( 0, session.createQuery("from Account", Account.class).getResultList().size() );
|
||||
session.disableFilter(TenantIdBinder.FILTER_NAME);
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
|
@ -247,9 +249,9 @@ public class TenantIdTest implements SessionFactoryProducer {
|
|||
Record r = em.find( Record.class, record.id );
|
||||
assertEquals( "mine", r.state.tenantId );
|
||||
|
||||
// Session seems to not apply tenant-id on #find
|
||||
// HHH-16830 Session applies tenant-id on #find
|
||||
Record yours = em.find( Record.class, record2.id );
|
||||
assertEquals( "yours", yours.state.tenantId );
|
||||
assertNull(yours);
|
||||
|
||||
|
||||
em.createQuery( "from Record where id = :id", Record.class )
|
||||
|
|
|
@ -79,7 +79,8 @@ public class TenantLongIdTest implements SessionFactoryProducer {
|
|||
|
||||
currentTenant = yours;
|
||||
scope.inTransaction( session -> {
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
//HHH-16830 Sessions applies tenantId filter on find()
|
||||
assertNull( session.find(Account.class, acc.id) );
|
||||
assertEquals( 0, session.createQuery("from Account").getResultList().size() );
|
||||
session.disableFilter(TenantIdBinder.FILTER_NAME);
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
|
|||
import java.util.UUID;
|
||||
|
||||
import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -86,7 +87,8 @@ public class TenantUuidTest implements SessionFactoryProducer {
|
|||
|
||||
currentTenant = yours;
|
||||
scope.inTransaction( session -> {
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
//HHH-16830 Sessions applies tenantId filter on find()
|
||||
assertNull( session.find(Account.class, acc.id) );
|
||||
assertEquals( 0, session.createQuery("from Account").getResultList().size() );
|
||||
session.disableFilter(TenantIdBinder.FILTER_NAME);
|
||||
assertNotNull( session.find(Account.class, acc.id) );
|
||||
|
|
Loading…
Reference in New Issue