From 129a7085fa9b7d33f2818286eee77177460728a3 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 23 May 2016 20:12:24 -0500 Subject: [PATCH] HHH-11025 - Document relation traversal api. (cherry picked from commit 6be8232) --- .../userguide/chapters/envers/Envers.adoc | 117 +++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 3ecd58a24a..eeb172c935 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -626,6 +626,119 @@ Other queries (also accessible from `org.hibernate.envers.CrossTypeRevisionChang Note that methods described above can be legally used only when the default mechanism of tracking changed entity names is enabled (see <>). +[[envers-querying-entity-relation-jobs]] +=== 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] +==== +Relation join queries are considered experimental and may change in future releases. +==== + +[NOTE] +==== +Relation joins can only be applied to `*-to-one` mappings and can only be specified using `JoinType.LEFT` or +`JoinType.INNER`. +==== + +The basis for creating an entity relation join query is as follows: + +[source,java] +---- +// create an inner join query +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.INNER ); + +// create a left join query +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.LEFT ); +---- + +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] +---- +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.INNER ) + .add( AuditEntity.property( "name" ).like( "Joe%" ) ); +---- + +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] +---- +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.INNER ) + .traverseRelation( "address", JoinType.INNER ) + .add( AuditEntity.property( "streetNumber" ).eq( 1234 ) ); +---- + +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 +the owner's age is `20` or that the car has _no_ owner: + +[source,java] +---- +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.LEFT, "p" ) + .up() + .add( + AuditEntity.or( + AuditEntity.property( "p", "age" ).eq( 20 ), + AuditEntity.relatedId( "owner" ).eq( null ) + ) + ) + .addOrder( AuditEntity.property( "make" ).asc() ); +---- + +[NOTE] +==== +Queries can use the `up` method to navigate back up the entity graph. +==== + +Disjunction criterion may also be applied to relation join queries. For example, the following query will find all +`Car` entities where the owner's age is `20` or that the owner lives at an address where the street number equals `1234`: + +[source,java] +---- +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.INNER, "p" ) + .traverseRelation( "address", JoinType.INNER, "a" ) + .up() + .up() + .add( + AuditEntity.disjunction() + .add( AuditEntity.property( "p", "age" ).eq( 20 ) ) + .add( AuditEntity.property( "a", "streetNumber" ).eq( 1234 ) + ) + ) + .addOrder( AuditEntity.property( "make" ).asc() ); +---- + +Lastly, this example illustrates how related entity properties can be compared as a constraint. This query shows how to +find the `Car` entities where the owner's `age` equals the `streetNumber` of where the owner lives: + +[source,java] +---- +AuditQuery query = getAuditReader().createQuery() + .forEntitiesAtRevision( Car.class, 1 ) + .traverseRelation( "owner", JoinType.INNER, "p" ) + .traverseRelation( "address", JoinType.INNER, "a" ) + .up() + .up() + .add( AuditEntity.property( "p", "age" ).eqProperty( "a", "streetNumber" ) ); +---- + === Conditional auditing Envers persists audit data in reaction to various Hibernate events (e.g. `post update`, `post insert`, and so on), using a series of event listeners from the `org.hibernate.envers.event.spi` package. @@ -776,7 +889,7 @@ alter table Person [[envers-mappingexceptions]] === Mapping exceptions -=== What isn't and will not be supported +==== What isn't and will not be supported Bags are not supported because they can contain non-unique elements. Persisting, a bag of `String`s violates the relational database principle that each table is a set of tuples. @@ -789,7 +902,7 @@ There are at least two ways out if you need bag semantics: . use an indexed collection, with the `@javax.persistence.OrderColumn` annotation . provide a unique id for your elements with the `@CollectionId` annotation. -=== What isn't and _will_ be supported +==== What isn't and _will_ be supported . Bag style collections with a `@CollectionId` identifier column (see https://hibernate.atlassian.net/browse/HHH-3950[HHH-3950]).