HHH-11886 - Elaborate Envers documentation and switch to actual source code examples

Migarte code snippets to test cases for Join relations queries
This commit is contained in:
Vlad Mihalcea 2017-09-06 12:51:32 +03:00
parent 02c5996571
commit 742d8be69c
8 changed files with 498 additions and 85 deletions

View File

@ -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). 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. 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 === 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 [WARNING]
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. 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] [NOTE]
==== ====
Relation joins can only be applied to `*-to-one` mappings and can only be specified using `JoinType.LEFT` or Relation joins can be applied to `many-to-one` and `many-to-one` mappings only when using `JoinType.LEFT` or `JoinType.INNER`.
`JoinType.INNER`.
==== ====
The basis for creating an entity relation join query is as follows: 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 include::{sourcedir}/QueryAuditTest.java[tags=envers-querying-entity-relation-inner-join]
AuditQuery query = getAuditReader().createQuery() ----
.forEntitiesAtRevision( Car.class, 1 ) ====
.traverseRelation( "owner", JoinType.INNER );
// create a left join query [[envers-querying-entity-relation-left-join]]
AuditQuery query = getAuditReader().createQuery() .LEFT JOIN entity audit query
.forEntitiesAtRevision( Car.class, 1 ) ====
.traverseRelation( "owner", JoinType.LEFT ); [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 [source, SQL, indent=0]
have an owner with a name starting with `Joe`, you would use:
[source,java]
---- ----
AuditQuery query = getAuditReader().createQuery() include::{extrasdir}/envers-querying-entity-relation-join-restriction.sql[]
.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 `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 [source, SQL, indent=0]
where the owner's address has a street number that equals `1234`:
[source,java]
---- ----
AuditQuery query = getAuditReader().createQuery() include::{extrasdir}/envers-querying-entity-relation-nested-join-restriction.sql[]
.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 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 relation state, such as testing for `null`. For example, the following query illustrates how to find all `Car` entities where

View File

@ -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]

View File

@ -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]

View File

@ -1,23 +1,23 @@
select select
queryaudit0_.id as id1_3_, c.id as id1_3_,
queryaudit0_.REV as REV2_3_, c.REV as REV2_3_,
queryaudit0_.REVTYPE as REVTYPE3_3_, c.REVTYPE as REVTYPE3_3_,
queryaudit0_.REVEND as REVEND4_3_, c.REVEND as REVEND4_3_,
queryaudit0_.created_on as created_5_3_, c.created_on as created_5_3_,
queryaudit0_.createdOn_MOD as createdO6_3_, c.createdOn_MOD as createdO6_3_,
queryaudit0_.firstName as firstNam7_3_, c.firstName as firstNam7_3_,
queryaudit0_.firstName_MOD as firstNam8_3_, c.firstName_MOD as firstNam8_3_,
queryaudit0_.lastName as lastName9_3_, c.lastName as lastName9_3_,
queryaudit0_.lastName_MOD as lastNam10_3_, c.lastName_MOD as lastNam10_3_,
queryaudit0_.address_id as address11_3_, c.address_id as address11_3_,
queryaudit0_.address_MOD as address12_3_ c.address_MOD as address12_3_
from from
Customer_AUD queryaudit0_ Customer_AUD c
where where
queryaudit0_.REV=? c.REV=?
and queryaudit0_.id=? and c.id=?
and queryaudit0_.lastName_MOD=? and c.lastName_MOD=?
and queryaudit0_.firstName_MOD=? and c.firstName_MOD=?
-- binding parameter [1] as [INTEGER] - [2] -- binding parameter [1] as [INTEGER] - [2]
-- binding parameter [2] as [BIGINT] - [1] -- binding parameter [2] as [BIGINT] - [1]

View File

@ -1,29 +1,29 @@
select select
queryaudit0_.id as id1_3_0_, c.id as id1_3_0_,
queryaudit0_.REV as REV2_3_0_, c.REV as REV2_3_0_,
defaultrev1_.REV as REV1_4_1_, defaultrev1_.REV as REV1_4_1_,
queryaudit0_.REVTYPE as REVTYPE3_3_0_, c.REVTYPE as REVTYPE3_3_0_,
queryaudit0_.REVEND as REVEND4_3_0_, c.REVEND as REVEND4_3_0_,
queryaudit0_.created_on as created_5_3_0_, c.created_on as created_5_3_0_,
queryaudit0_.createdOn_MOD as createdO6_3_0_, c.createdOn_MOD as createdO6_3_0_,
queryaudit0_.firstName as firstNam7_3_0_, c.firstName as firstNam7_3_0_,
queryaudit0_.firstName_MOD as firstNam8_3_0_, c.firstName_MOD as firstNam8_3_0_,
queryaudit0_.lastName as lastName9_3_0_, c.lastName as lastName9_3_0_,
queryaudit0_.lastName_MOD as lastNam10_3_0_, c.lastName_MOD as lastNam10_3_0_,
queryaudit0_.address_id as address11_3_0_, c.address_id as address11_3_0_,
queryaudit0_.address_MOD as address12_3_0_, c.address_MOD as address12_3_0_,
defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ defaultrev1_.REVTSTMP as REVTSTMP2_4_1_
from from
Customer_AUD queryaudit0_ cross Customer_AUD c cross
join join
REVINFO defaultrev1_ REVINFO defaultrev1_
where where
queryaudit0_.id=? c.id=?
and queryaudit0_.lastName_MOD=? and c.lastName_MOD=?
and queryaudit0_.firstName_MOD=? and c.firstName_MOD=?
and queryaudit0_.REV=defaultrev1_.REV and c.REV=defaultrev1_.REV
order by order by
queryaudit0_.REV asc c.REV asc
-- binding parameter [1] as [BIGINT] - [1] -- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BOOLEAN] - [true] -- binding parameter [2] as [BOOLEAN] - [true]

View File

@ -1,28 +1,28 @@
select select
queryaudit0_.id as id1_3_0_, c.id as id1_3_0_,
queryaudit0_.REV as REV2_3_0_, c.REV as REV2_3_0_,
defaultrev1_.REV as REV1_4_1_, defaultrev1_.REV as REV1_4_1_,
queryaudit0_.REVTYPE as REVTYPE3_3_0_, c.REVTYPE as REVTYPE3_3_0_,
queryaudit0_.REVEND as REVEND4_3_0_, c.REVEND as REVEND4_3_0_,
queryaudit0_.created_on as created_5_3_0_, c.created_on as created_5_3_0_,
queryaudit0_.createdOn_MOD as createdO6_3_0_, c.createdOn_MOD as createdO6_3_0_,
queryaudit0_.firstName as firstNam7_3_0_, c.firstName as firstNam7_3_0_,
queryaudit0_.firstName_MOD as firstNam8_3_0_, c.firstName_MOD as firstNam8_3_0_,
queryaudit0_.lastName as lastName9_3_0_, c.lastName as lastName9_3_0_,
queryaudit0_.lastName_MOD as lastNam10_3_0_, c.lastName_MOD as lastNam10_3_0_,
queryaudit0_.address_id as address11_3_0_, c.address_id as address11_3_0_,
queryaudit0_.address_MOD as address12_3_0_, c.address_MOD as address12_3_0_,
defaultrev1_.REVTSTMP as REVTSTMP2_4_1_ defaultrev1_.REVTSTMP as REVTSTMP2_4_1_
from from
Customer_AUD queryaudit0_ cross Customer_AUD c cross
join join
REVINFO defaultrev1_ REVINFO defaultrev1_
where where
queryaudit0_.id = ? c.id = ?
and queryaudit0_.lastName_MOD = ? and c.lastName_MOD = ?
and queryaudit0_.REV=defaultrev1_.REV and c.REV=defaultrev1_.REV
order by order by
queryaudit0_.REV asc c.REV asc
-- binding parameter [1] as [BIGINT] - [1] -- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BOOLEAN] - [true] -- binding parameter [2] as [BOOLEAN] - [true]

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Number> 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;
}
}
}

View File

@ -19,6 +19,7 @@ import javax.persistence.Id;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.persistence.criteria.JoinType;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.envers.AuditReaderFactory; import org.hibernate.envers.AuditReaderFactory;
@ -225,6 +226,39 @@ public class QueryAuditTest extends BaseEntityManagerFunctionalTestCase {
assertEquals( 1, revision ); 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 @Audited