From ca9084cb5395f071884d39a5f6e6825b05d2a24d Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 28 Jul 2016 15:20:59 +0300 Subject: [PATCH] HHH-11002 - Add documentation section about Filter and FilterDef --- .../chapters/domain/basic_types.adoc | 136 +++++++++- ...apping-filter-collection-query-example.sql | 42 +++ .../mapping-filter-entity-query-example.sql | 23 ++ .../mapping-filter-persistence-example.sql | 11 + ...mapping-where-collection-query-example.sql | 25 ++ .../mapping-where-entity-query-example.sql | 10 + .../mapping-where-persistence-example.sql | 11 + .../userguide/mapping/basic/FilterTest.java | 251 ++++++++++++++++++ .../userguide/mapping/basic/WhereTest.java | 226 ++++++++++++++++ 9 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-collection-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-entity-query-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-persistence-example.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc index e17fcc077d..b601e05e36 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -1186,7 +1186,7 @@ include::{extrasdir}/basic/mapping-column-read-and-write-composite-type-persiste ==== [[mapping-column-formula]] -==== Formula +==== @Formula Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment) @@ -1226,6 +1226,140 @@ include::{extrasdir}/basic/mapping-column-formula-persistence-example.sql[] The SQL fragment can be as complex as you want and even include subselects. ==== +[[mapping-column-where]] +==== @Where + +Sometimes, you want to filter out entities or collections using a custom SQL criteria. +This can be achieved using the `@Where` annotation, which can be applied to entities and collections. + +[[mapping-where-example]] +.`@Where` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereTest.java[tags=mapping-where-example] +---- +==== + +If the database contains the following entities: + +[[mapping-where-persistence-example]] +.Persisting an fetching entities with a `@Where` mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereTest.java[tags=mapping-where-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-where-persistence-example.sql[] +---- +==== + +When executing an `Account` entity query, Hibernate is going to filter out all records that are not active. + +[[mapping-where-entity-query-example]] +.Query entities mapped with `@Where` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereTest.java[tags=mapping-where-entity-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-where-entity-query-example.sql[] +---- +==== + +When fetching the `debitAccounts` or the `creditAccounts` collections, Hibernate is going to apply the `@Where` clause filtering criteria to the associated child entities. + +[[mapping-where-collection-query-example]] +.Traversing collections mapped with `@Where` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/WhereTest.java[tags=mapping-where-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-where-collection-query-example.sql[] +---- +==== + +[[mapping-column-filter]] +==== @Filter + +The `@Filter` annotation is another way to filter out entities or collections using a custom SQL criteria, for both entities and collections. +Unlike the `@Where` annotation, `@Filter` allows you to parameterize the filter clause at runtime. + +[[mapping-filter-example]] +.`@Filter` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-example] +---- +==== + +If the database contains the following entities: + +[[mapping-filter-persistence-example]] +.Persisting an fetching entities with a `@Filter` mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-filter-persistence-example.sql[] +---- +==== + +By default, without explicitly enabling the filter, Hibernate is going to fetch all `Account` entities. +If the filter is enabled and the filter parameter value is provided, +then Hibernate is going to apply the filtering criteria to the associated `Account` entities. + +[[mapping-filter-entity-query-example]] +.Query entities mapped with `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-entity-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-filter-entity-query-example.sql[] +---- +==== + +Jut like with entities, collections can be filtered as well, but only if the filter is explicilty enabled on the currently running Hibernate `Session`. +This way, when fetching the `accounts` collections, Hibernate is going to apply the `@Filter` clause filtering criteria to the associated collection entries. + +[[mapping-filter-collection-query-example]] +.Traversing collections mapped with `@Filter` +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FilterTest.java[tags=mapping-filter-collection-query-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-filter-collection-query-example.sql[] +---- +==== + +[NOTE] +==== +The main advantage of `@Filter` over teh `@Where` clause is that the filtering criteria can be customized at runtime. +==== + [[mapping-column-any]] ==== @Any mapping diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql new file mode 100644 index 0000000000..d3d216ce8d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-collection-query-example.sql @@ -0,0 +1,42 @@ +SELECT + c.id as id1_1_0_, + c.name as name2_1_0_ +FROM + Client c +WHERE + c.id = 1 + +SELECT + a.id as id1_0_, + a.active as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + a.client_id = 1 + +-- Activate filter [activeAccount] + +SELECT + c.id as id1_1_0_, + c.name as name2_1_0_ +FROM + Client c +WHERE + c.id = 1 + +SELECT + a.id as id1_0_, + a.active as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + accounts0_.active = true + and a.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql new file mode 100644 index 0000000000..88c17423eb --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-entity-query-example.sql @@ -0,0 +1,23 @@ +SELECT + a.id as id1_0_, + a.active as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a + +-- Activate filter [activeAccount] + +SELECT + a.id as id1_0_, + a.active as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE + a.active = true \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql new file mode 100644 index 0000000000..2f4abf9d66 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-filter-persistence-example.sql @@ -0,0 +1,11 @@ +INSERT INTO Client (name, id) +VALUES ('John Doe', 1) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-collection-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-collection-query-example.sql new file mode 100644 index 0000000000..132ad5e933 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-collection-query-example.sql @@ -0,0 +1,25 @@ +SELECT + c.client_id as client_i6_0_0_, + c.id as id1_0_0_, + c.id as id1_0_1_, + c.active as active2_0_1_, + c.amount as amount3_0_1_, + c.client_id as client_i6_0_1_, + c.rate as rate4_0_1_, + c.account_type as account_5_0_1_ +FROM + Account c +WHERE ( c.active = true and c.account_type = 'CREDIT' ) AND c.client_id = 1 + +SELECT + d.client_id as client_i6_0_0_, + d.id as id1_0_0_, + d.id as id1_0_1_, + d.active as active2_0_1_, + d.amount as amount3_0_1_, + d.client_id as client_i6_0_1_, + d.rate as rate4_0_1_, + d.account_type as account_5_0_1_ +FROM + Account d +WHERE ( d.active = true and d.account_type = 'DEBIT' ) AND d.client_id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-entity-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-entity-query-example.sql new file mode 100644 index 0000000000..2f101b347c --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-entity-query-example.sql @@ -0,0 +1,10 @@ +SELECT + a.id as id1_0_, + a.active as active2_0_, + a.amount as amount3_0_, + a.client_id as client_i6_0_, + a.rate as rate4_0_, + a.account_type as account_5_0_ +FROM + Account a +WHERE ( a.active = true ) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-persistence-example.sql new file mode 100644 index 0000000000..2f4abf9d66 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-where-persistence-example.sql @@ -0,0 +1,11 @@ +INSERT INTO Client (name, id) +VALUES ('John Doe', 1) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (true, 5000.0, 1, 0.0125, 'CREDIT', 1) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (false, 0.0, 1, 0.0105, 'DEBIT', 2) + +INSERT INTO Account (active, amount, client_id, rate, account_type, id) +VALUES (true, 250.0, 1, 0.0105, 'DEBIT', 3) \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java new file mode 100644 index 0000000000..b2cf429a48 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FilterTest.java @@ -0,0 +1,251 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Session; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.userguide.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class FilterTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger log = Logger.getLogger( FilterTest.class ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-filter-persistence-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + Client client = new Client(); + client.setId( 1L ); + client.setName( "John Doe" ); + entityManager.persist( client ); + + Account account1 = new Account( ); + account1.setId( 1L ); + account1.setType( AccountType.CREDIT ); + account1.setAmount( 5000d ); + account1.setRate( 1.25 / 100 ); + account1.setActive( true ); + account1.setClient( client ); + client.getAccounts().add( account1 ); + entityManager.persist( account1 ); + + Account account2 = new Account( ); + account2.setId( 2L ); + account2.setType( AccountType.DEBIT ); + account2.setAmount( 0d ); + account2.setRate( 1.05 / 100 ); + account2.setActive( false ); + account2.setClient( client ); + client.getAccounts().add( account2 ); + entityManager.persist( account2 ); + + Account account3 = new Account( ); + account3.setType( AccountType.DEBIT ); + account3.setId( 3L ); + account3.setAmount( 250d ); + account3.setRate( 1.05 / 100 ); + account3.setActive( true ); + account3.setClient( client ); + client.getAccounts().add( account3 ); + entityManager.persist( account3 ); + } ); + //end::mapping-filter-persistence-example[] + + + //tag::mapping-filter-entity-query-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + assertEquals( 3, accounts.size()); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + assertEquals( 2, accounts.size()); + } ); + //end::mapping-filter-entity-query-example[] + + //tag::mapping-filter-collection-query-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Client client = entityManager.find( Client.class, 1L ); + assertEquals( 3, client.getAccounts().size() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + log.infof( "Activate filter [%s]", "activeAccount"); + + entityManager + .unwrap( Session.class ) + .enableFilter( "activeAccount" ) + .setParameter( "active", true); + + Client client = entityManager.find( Client.class, 1L ); + assertEquals( 2, client.getAccounts().size() ); + } ); + //end::mapping-filter-collection-query-example[] + } + + //tag::mapping-filter-example[] + public enum AccountType { + DEBIT, + CREDIT + } + + @Entity(name = "Client") + public static class Client { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "client") + @Filter(name="activeAccount", condition="active = :active") + private List accounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + + //end::mapping-filter-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getAccounts() { + return accounts; + } + //tag::mapping-filter-example[] + } + + @Entity(name = "Account") + @FilterDef(name="activeAccount", parameters=@ParamDef( name="active", type="boolean" ) ) + @Filter(name="activeAccount", condition="active = :active") + public static class Account { + + @Id + private Long id; + + @ManyToOne + private Client client; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + private boolean active; + + //Getters and setters omitted for brevity + + //end::mapping-filter-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public AccountType getType() { + return type; + } + + public void setType(AccountType type) { + this.type = type; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public Double getRate() { + return rate; + } + + public void setRate(Double rate) { + this.rate = rate; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + //tag::mapping-filter-example[] + } + //end::mapping-filter-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereTest.java new file mode 100644 index 0000000000..ef8628e333 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/WhereTest.java @@ -0,0 +1,226 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.Where; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.userguide.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class WhereTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Client.class, + Account.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-where-persistence-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + + Client client = new Client(); + client.setId( 1L ); + client.setName( "John Doe" ); + entityManager.persist( client ); + + Account account1 = new Account( ); + account1.setId( 1L ); + account1.setType( AccountType.CREDIT ); + account1.setAmount( 5000d ); + account1.setRate( 1.25 / 100 ); + account1.setActive( true ); + account1.setClient( client ); + client.getCreditAccounts().add( account1 ); + entityManager.persist( account1 ); + + Account account2 = new Account( ); + account2.setId( 2L ); + account2.setType( AccountType.DEBIT ); + account2.setAmount( 0d ); + account2.setRate( 1.05 / 100 ); + account2.setActive( false ); + account2.setClient( client ); + client.getDebitAccounts().add( account2 ); + entityManager.persist( account2 ); + + Account account3 = new Account( ); + account3.setType( AccountType.DEBIT ); + account3.setId( 3L ); + account3.setAmount( 250d ); + account3.setRate( 1.05 / 100 ); + account3.setActive( true ); + account3.setClient( client ); + client.getDebitAccounts().add( account3 ); + entityManager.persist( account3 ); + } ); + //end::mapping-where-persistence-example[] + + + //tag::mapping-where-entity-query-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + List accounts = entityManager.createQuery( + "select a from Account a", Account.class) + .getResultList(); + assertEquals( 2, accounts.size()); + } ); + //end::mapping-where-entity-query-example[] + + //tag::mapping-where-collection-query-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + Client client = entityManager.find( Client.class, 1L ); + assertEquals( 1, client.getCreditAccounts().size() ); + assertEquals( 1, client.getDebitAccounts().size() ); + } ); + //end::mapping-where-collection-query-example[] + } + + //tag::mapping-where-example[] + public enum AccountType { + DEBIT, + CREDIT + } + + @Entity(name = "Client") + public static class Client { + + @Id + private Long id; + + private String name; + + @Where( clause = "account_type = 'DEBIT'") + @OneToMany(mappedBy = "client") + private List debitAccounts = new ArrayList<>( ); + + @Where( clause = "account_type = 'CREDIT'") + @OneToMany(mappedBy = "client") + private List creditAccounts = new ArrayList<>( ); + + //Getters and setters omitted for brevity + + //end::mapping-where-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getDebitAccounts() { + return debitAccounts; + } + + public List getCreditAccounts() { + return creditAccounts; + } + //tag::mapping-where-example[] + } + + @Entity(name = "Account") + @Where( clause = "active = true" ) + public static class Account { + + @Id + private Long id; + + @ManyToOne + private Client client; + + @Column(name = "account_type") + @Enumerated(EnumType.STRING) + private AccountType type; + + private Double amount; + + private Double rate; + + private boolean active; + + //Getters and setters omitted for brevity + + //end::mapping-where-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public AccountType getType() { + return type; + } + + public void setType(AccountType type) { + this.type = type; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public Double getRate() { + return rate; + } + + public void setRate(Double rate) { + this.rate = rate; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + //tag::mapping-where-example[] + } + //end::mapping-where-example[] +}