From 742d8be69ca81b4e3f8c687f81712f4f932e40a9 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 6 Sep 2017 12:51:32 +0300 Subject: [PATCH] HHH-11886 - Elaborate Envers documentation and switch to actual source code examples Migarte code snippets to test cases for Join relations queries --- .../userguide/chapters/envers/Envers.adoc | 85 +++--- ...rying-entity-relation-join-restriction.sql | 42 +++ ...ntity-relation-nested-join-restriction.sql | 60 ++++ ...es-changes-queries-at-revision-example.sql | 34 +-- ...s-hasChanged-and-hasNotChanged-example.sql | 36 +-- ...ies-changes-queries-hasChanged-example.sql | 34 +-- .../envers/QueryAuditAdressCountryTest.java | 258 ++++++++++++++++++ .../userguide/envers/QueryAuditTest.java | 34 +++ 8 files changed, 498 insertions(+), 85 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index f045298fef..db89a11e31 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -959,60 +959,79 @@ Other queries (also accessible from `org.hibernate.envers.CrossTypeRevisionChang Returns a map containing lists of entity snapshots grouped by modification operation (e.g. addition, update and removal). Executes `3N+1` SQL queries, where `N` is a number of different entity classes modified within specified revision. -[[envers-querying-entity-relation-jobs]] +[[envers-querying-entity-relation-joins]] === Querying for entities using entity relation joins -Audit queries support the ability to apply constraints, projections, and sort operations based on entity relations. In order -to traverse entity relations through an audit query, you must use the relation traversal API with a join type. - -[IMPORTANT] +[WARNING] ==== Relation join queries are considered experimental and may change in future releases. ==== +Audit queries support the ability to apply constraints, projections, and sort operations based on entity relations. In order +to traverse entity relations through an audit query, you must use the relation traversal API with a join type. + [NOTE] ==== -Relation joins can only be applied to `*-to-one` mappings and can only be specified using `JoinType.LEFT` or -`JoinType.INNER`. +Relation joins can be applied to `many-to-one` and `many-to-one` mappings only when using `JoinType.LEFT` or `JoinType.INNER`. ==== The basis for creating an entity relation join query is as follows: -[source,java] +[[envers-querying-entity-relation-inner-join]] +.INNER JOIN entity audit query +==== +[source, JAVA, indent=0] ---- -// create an inner join query -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ); +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-inner-join] +---- +==== -// create a left join query -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.LEFT ); +[[envers-querying-entity-relation-left-join]] +.LEFT JOIN entity audit query +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-left-join] +---- +==== + +Like any other query, constraints may be added to restrict the results. + +For example, to find all `Customers` entities whose addresses are in `România`, +you can use the following query: + +[[envers-querying-entity-relation-join-restriction]] +.Filtering the join relation with a WHERE clause predicate +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-join-restriction] ---- -Like any other query, constraints may be added to restrict the results. For example, to find all `Car` entities that -have an owner with a name starting with `Joe`, you would use: - -[source,java] +[source, SQL, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ) - .add( AuditEntity.property( "name" ).like( "Joe%" ) ); +include::{extrasdir}/envers-querying-entity-relation-join-restriction.sql[] +---- +==== + +It is also possible to traverse beyond the first relation in an entity graph. + +For example, to find all `Customer` entities +where the country entity belonging to the address attribute is `România`: + +[[envers-querying-entity-relation-nested-join-restriction]] +.Filtering a nested join relation with a WHERE clause predicate +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/QueryAuditAdressCountryTest.java[tags=envers-querying-entity-relation-nested-join-restriction] ---- -It is also possible to traverse beyond the first relation in an entity graph. For example, to find all `Car` entities -where the owner's address has a street number that equals `1234`: - -[source,java] +[source, SQL, indent=0] ---- -AuditQuery query = getAuditReader().createQuery() - .forEntitiesAtRevision( Car.class, 1 ) - .traverseRelation( "owner", JoinType.INNER ) - .traverseRelation( "address", JoinType.INNER ) - .add( AuditEntity.property( "streetNumber" ).eq( 1234 ) ); +include::{extrasdir}/envers-querying-entity-relation-nested-join-restriction.sql[] ---- +==== Complex constraints may also be added that are applicable to properties of nested relations or the base query entity or relation state, such as testing for `null`. For example, the following query illustrates how to find all `Car` entities where diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql new file mode 100644 index 0000000000..865858d310 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-join-restriction.sql @@ -0,0 +1,42 @@ +select + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.firstName as firstNam6_3_, + c.lastName as lastName7_3_, + c.address_id as address_8_3_ +from + Customer_AUD c +inner join + Address_AUD a + on ( + c.address_id=a.id + or ( + c.address_id is null + ) + and ( + a.id is null + ) + ) +where + c.REV<=? + and c.REVTYPE<>? + and ( + c.REVEND>? + or c.REVEND is null + ) + and a.REV<=? + and a.country=? + and ( + a.REVEND>? + or a.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [INTEGER] - [1] +-- binding parameter [5] as [VARCHAR] - [România] +-- binding parameter [6] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql new file mode 100644 index 0000000000..4ca5a8f788 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-querying-entity-relation-nested-join-restriction.sql @@ -0,0 +1,60 @@ +select + cu.id as id1_5_, + cu.REV as REV2_5_, + cu.REVTYPE as REVTYPE3_5_, + cu.REVEND as REVEND4_5_, + cu.created_on as created_5_5_, + cu.firstName as firstNam6_5_, + cu.lastName as lastName7_5_, + cu.address_id as address_8_5_ +from + Customer_AUD cu +inner join + Address_AUD a + on ( + cu.address_id=a.id + or ( + cu.address_id is null + ) + and ( + a.id is null + ) + ) +inner join + Country_AUD co + on ( + a.country_id=co.id + or ( + a.country_id is null + ) + and ( + co.id is null + ) + ) +where + cu.REV<=? + and cu.REVTYPE<>? + and ( + cu.REVEND>? + or cu.REVEND is null + ) + and a.REV<=? + and ( + a.REVEND>? + or a.REVEND is null + ) + and co.REV<=? + and co.name=? + and ( + co.REVEND>? + or co.REVEND is null + ) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [INTEGER] - [2] +-- binding parameter [3] as [INTEGER] - [1] +-- binding parameter [4] as [INTEGER] - [1] +-- binding parameter [5] as [INTEGER] - [1] +-- binding parameter [6] as [INTEGER] - [1] +-- binding parameter [7] as [VARCHAR] - [România] +-- binding parameter [8] as [INTEGER] - [1] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql index 515c264526..33ca9a67aa 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-at-revision-example.sql @@ -1,23 +1,23 @@ select - queryaudit0_.id as id1_3_, - queryaudit0_.REV as REV2_3_, - queryaudit0_.REVTYPE as REVTYPE3_3_, - queryaudit0_.REVEND as REVEND4_3_, - queryaudit0_.created_on as created_5_3_, - queryaudit0_.createdOn_MOD as createdO6_3_, - queryaudit0_.firstName as firstNam7_3_, - queryaudit0_.firstName_MOD as firstNam8_3_, - queryaudit0_.lastName as lastName9_3_, - queryaudit0_.lastName_MOD as lastNam10_3_, - queryaudit0_.address_id as address11_3_, - queryaudit0_.address_MOD as address12_3_ + c.id as id1_3_, + c.REV as REV2_3_, + c.REVTYPE as REVTYPE3_3_, + c.REVEND as REVEND4_3_, + c.created_on as created_5_3_, + c.createdOn_MOD as createdO6_3_, + c.firstName as firstNam7_3_, + c.firstName_MOD as firstNam8_3_, + c.lastName as lastName9_3_, + c.lastName_MOD as lastNam10_3_, + c.address_id as address11_3_, + c.address_MOD as address12_3_ from - Customer_AUD queryaudit0_ + Customer_AUD c where - queryaudit0_.REV=? - and queryaudit0_.id=? - and queryaudit0_.lastName_MOD=? - and queryaudit0_.firstName_MOD=? + c.REV=? + and c.id=? + and c.lastName_MOD=? + and c.firstName_MOD=? -- binding parameter [1] as [INTEGER] - [2] -- binding parameter [2] as [BIGINT] - [1] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql index dfd6f359fa..f3579a0416 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-and-hasNotChanged-example.sql @@ -1,29 +1,29 @@ select - queryaudit0_.id as id1_3_0_, - queryaudit0_.REV as REV2_3_0_, + c.id as id1_3_0_, + c.REV as REV2_3_0_, defaultrev1_.REV as REV1_4_1_, - queryaudit0_.REVTYPE as REVTYPE3_3_0_, - queryaudit0_.REVEND as REVEND4_3_0_, - queryaudit0_.created_on as created_5_3_0_, - queryaudit0_.createdOn_MOD as createdO6_3_0_, - queryaudit0_.firstName as firstNam7_3_0_, - queryaudit0_.firstName_MOD as firstNam8_3_0_, - queryaudit0_.lastName as lastName9_3_0_, - queryaudit0_.lastName_MOD as lastNam10_3_0_, - queryaudit0_.address_id as address11_3_0_, - queryaudit0_.address_MOD as address12_3_0_, + c.REVTYPE as REVTYPE3_3_0_, + c.REVEND as REVEND4_3_0_, + c.created_on as created_5_3_0_, + c.createdOn_MOD as createdO6_3_0_, + c.firstName as firstNam7_3_0_, + c.firstName_MOD as firstNam8_3_0_, + c.lastName as lastName9_3_0_, + c.lastName_MOD as lastNam10_3_0_, + c.address_id as address11_3_0_, + c.address_MOD as address12_3_0_, defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ from - Customer_AUD queryaudit0_ cross + Customer_AUD c cross join REVINFO defaultrev1_ where - queryaudit0_.id=? - and queryaudit0_.lastName_MOD=? - and queryaudit0_.firstName_MOD=? - and queryaudit0_.REV=defaultrev1_.REV + c.id=? + and c.lastName_MOD=? + and c.firstName_MOD=? + and c.REV=defaultrev1_.REV order by - queryaudit0_.REV asc + c.REV asc -- binding parameter [1] as [BIGINT] - [1] -- binding parameter [2] as [BOOLEAN] - [true] diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql index 236eed03a1..a466fb8f3e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-properties-changes-queries-hasChanged-example.sql @@ -1,28 +1,28 @@ select - queryaudit0_.id as id1_3_0_, - queryaudit0_.REV as REV2_3_0_, + c.id as id1_3_0_, + c.REV as REV2_3_0_, defaultrev1_.REV as REV1_4_1_, - queryaudit0_.REVTYPE as REVTYPE3_3_0_, - queryaudit0_.REVEND as REVEND4_3_0_, - queryaudit0_.created_on as created_5_3_0_, - queryaudit0_.createdOn_MOD as createdO6_3_0_, - queryaudit0_.firstName as firstNam7_3_0_, - queryaudit0_.firstName_MOD as firstNam8_3_0_, - queryaudit0_.lastName as lastName9_3_0_, - queryaudit0_.lastName_MOD as lastNam10_3_0_, - queryaudit0_.address_id as address11_3_0_, - queryaudit0_.address_MOD as address12_3_0_, + c.REVTYPE as REVTYPE3_3_0_, + c.REVEND as REVEND4_3_0_, + c.created_on as created_5_3_0_, + c.createdOn_MOD as createdO6_3_0_, + c.firstName as firstNam7_3_0_, + c.firstName_MOD as firstNam8_3_0_, + c.lastName as lastName9_3_0_, + c.lastName_MOD as lastNam10_3_0_, + c.address_id as address11_3_0_, + c.address_MOD as address12_3_0_, defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ from - Customer_AUD queryaudit0_ cross + Customer_AUD c cross join REVINFO defaultrev1_ where - queryaudit0_.id = ? - and queryaudit0_.lastName_MOD = ? - and queryaudit0_.REV=defaultrev1_.REV + c.id = ? + and c.lastName_MOD = ? + and c.REV=defaultrev1_.REV order by - queryaudit0_.REV asc + c.REV asc -- binding parameter [1] as [BIGINT] - [1] -- binding parameter [2] as [BOOLEAN] - [true] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java new file mode 100644 index 0000000000..55e8052766 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditAdressCountryTest.java @@ -0,0 +1,258 @@ +/* + * 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.envers; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.criteria.JoinType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.query.AuditQuery; +import org.hibernate.envers.strategy.ValidityAuditStrategy; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class QueryAuditAdressCountryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + Address.class, + Country.class + }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( + EnversSettings.AUDIT_STRATEGY, + ValidityAuditStrategy.class.getName() + ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Country country = new Country(); + country.setId( 1L ); + country.setName( "România" ); + entityManager.persist( country ); + + Address address = new Address(); + address.setId( 1L ); + address.setCountry( country ); + address.setCity( "Cluj-Napoca" ); + address.setStreet( "Bulevardul Eroilor" ); + address.setStreetNumber( "1 A" ); + entityManager.persist( address ); + + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + customer.setAddress( address ); + + entityManager.persist( customer ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.find( Customer.class, 1L ); + customer.setLastName( "Doe Jr." ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = entityManager.getReference( Customer.class, 1L ); + entityManager.remove( customer ); + } ); + + List revisions = doInJPA( this::entityManagerFactory, entityManager -> { + return AuditReaderFactory.get( entityManager ).getRevisions( + Customer.class, + 1L + ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-nested-join-restriction[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ) + .traverseRelation( "country", JoinType.INNER ) + .add( AuditEntity.property( "name" ).eq( "România" ) ) + .getSingleResult(); + //end::envers-querying-entity-relation-nested-join-restriction[] + } ); + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + @ManyToOne(fetch = FetchType.LAZY) + private Address address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Audited + @Entity(name = "Address") + public static class Address { + + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Country country; + + private String city; + + private String street; + + private String streetNumber; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(String streetNumber) { + this.streetNumber = streetNumber; + } + } + + @Audited + @Entity(name = "Country") + public static class Country { + + @Id + private Long id; + + private String name; + + 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; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java index 211ae98c4b..50ae486e9e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/envers/QueryAuditTest.java @@ -19,6 +19,7 @@ import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.criteria.JoinType; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.envers.AuditReaderFactory; @@ -225,6 +226,39 @@ public class QueryAuditTest extends BaseEntityManagerFunctionalTestCase { assertEquals( 1, revision ); } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + { + //tag::envers-querying-entity-relation-inner-join[] + AuditQuery innerJoinAuditQuery = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ); + //end::envers-querying-entity-relation-inner-join[] + } + { + //tag::envers-querying-entity-relation-left-join[] + AuditQuery innerJoinAuditQuery = AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.LEFT ); + //end::envers-querying-entity-relation-left-join[] + } + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::envers-querying-entity-relation-join-restriction[] + Customer customer = (Customer) AuditReaderFactory + .get( entityManager ) + .createQuery() + .forEntitiesAtRevision( Customer.class, 1 ) + .traverseRelation( "address", JoinType.INNER ) + .add( AuditEntity.property( "country" ).eq( "România" ) ) + .getSingleResult(); + //end::envers-querying-entity-relation-join-restriction[] + } ); } @Audited