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

Refactor section about queries
This commit is contained in:
Vlad Mihalcea 2017-07-31 16:58:27 +03:00
parent 6ad8302a3c
commit 12616d44e2
5 changed files with 424 additions and 29 deletions

View File

@ -670,8 +670,8 @@ To see how "Modified Flags" can be utilized, check out the very simple query API
You can think of historic data as having two dimensions:
horizontal:: is the state of the database at a given revision. Thus, you can query for entities as they were at revision N.
vertical:: are the revisions, at which entities changed. Hence, you can query for revisions, in which a given entity changed.
horizontal:: The state of the database at a given revision. Thus, you can query for entities as they were at revision N.
vertical:: The revisions, at which entities changed. Hence, you can query for revisions, in which a given entity changed.
The queries in Envers are similar to Hibernate Criteria queries, so if you are common with them, using Envers queries will be much easier.
@ -679,57 +679,109 @@ The main limitation of the current queries implementation is that you cannot tra
You can only specify constraints on the ids of the related entities, and only on the "owning" side of the relation.
This however will be changed in future releases.
Please note, that queries on the audited data will be in many cases much slower than corresponding queries on "live" data, as they involve correlated subselects.
[NOTE]
====
The queries on the audited data will be in many cases much slower than corresponding queries on "live" data,
as, especially for the default audit strategy, they involve correlated subselects.
Queries are improved both in terms of speed and possibilities, when using the valid-time audit strategy, that is when storing both start and end revisions for entities. See <<envers-configuration>>.
Queries are improved both in terms of speed and possibilities, when using the validity audit strategy,
which stores both start and end revisions for entities. See <<envers-audit-ValidityAuditStrategy>>.
====
[[entities-at-revision]]
=== Querying for entities of a class at a given revision
The entry point for this type of queries is:
[source,java]
[[entities-at-revision-example]]
.Getting the `Customer` entity at a given revision
====
[source, JAVA, indent=0]
----
AuditQuery query = getAuditReader()
.createQuery()
.forEntitiesAtRevision( MyEntity.class, revisionNumber );
include::{sourcedir}/QueryAuditTest.java[tags=entities-at-revision-example]
----
====
[[entities-filtering]]
=== Querying for entities using filtering criteria
You can then specify constraints, which should be met by the entities returned, by adding restrictions,
which can be obtained using the `AuditEntity` factory class.
For example, to select only entities where the `firstName` property is equal to "John":
[[entities-filtering-example]]
.Getting the `Customer` audit log with a given `firstName` attribute value
====
[source, JAVA, indent=0]
----
include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-example]
----
====
And, to select only entities whose relationships are related to a given entity,
you can use either the target entity or its identifier.
[[entities-filtering-by-entity-example]]
.Getting the `Customer` entities whose `address` attribute matches the given entity reference
====
[source, JAVA, indent=0]
----
include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-by-entity-example]
----
You can then specify constraints, which should be met by the entities returned, by adding restrictions, which can be obtained using the `AuditEntity` factory class.
For example, to select only entities where the "name" property is equal to "John":
[source,java]
[source, SQL, indent=0]
----
query.add( AuditEntity.property( "name" ).eq( "John" ) );
include::{extrasdir}/entities-filtering-by-entity-example.sql[]
----
====
The same SQL is generated even if we provide the identifier instead of the target entity reference.
[[entities-filtering-by-entity-identifier-example]]
.Getting the `Customer` entities whose `address` identifier matches the given entity identifier
====
[source, JAVA, indent=0]
----
include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-by-entity-identifier-example]
----
====
Apart for strict equality matching, you can also use an `IN` clause to provide multiple entity identifiers:
[[entities-in-clause-filtering-by-entity-identifier-example]]
.Getting the `Customer` entities whose `address` identifier matches one of the given entity identifiers
====
[source, JAVA, indent=0]
----
include::{sourcedir}/QueryAuditTest.java[tags=entities-in-clause-filtering-by-entity-identifier-example]
----
And to select only entities that are related to a given entity:
[source,java]
[source, SQL, indent=0]
----
query.add( AuditEntity.property( "address" ).eq( relatedEntityInstance ) );
// or
query.add( AuditEntity.relatedId( "address" ).eq( relatedEntityId ) );
// or
query.add( AuditEntity.relatedId( "address" ).in( relatedEntityId1, relatedEntityId2 ) );
include::{extrasdir}/entities-in-clause-filtering-by-entity-identifier-example.sql[]
----
====
You can limit the number of results, order them, and set aggregations and projections (except grouping) in the usual way.
When your query is complete, you can obtain the results by calling the `getSingleResult()` or `getResultList()` methods.
A full query, can look for example like this:
[source,java]
[[entities-filtering-and-pagination]]
.Getting the `Customer` entities using filtering and pagination
====
[source, JAVA, indent=0]
----
List personsAtAddress = getAuditReader().createQuery()
.forEntitiesAtRevision( Person.class, 12 )
.addOrder( AuditEntity.property( "surname" ).desc() )
.add( AuditEntity.relatedId( "address" ).eq( addressId ) )
.setFirstResult( 4 )
.setMaxResults( 2 )
.getResultList();
include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-and-pagination]
----
[source, SQL, indent=0]
----
include::{extrasdir}/entities-filtering-and-pagination.sql[]
----
====
[[revisions-of-entity]]
=== Querying for revisions, at which entities of a given class changed

View File

@ -0,0 +1,17 @@
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
where
c.address_id = ?
order by
c.lastName desc
limit ?
offset ?

View File

@ -0,0 +1,17 @@
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
where
c.address_id = ?
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]

View File

@ -0,0 +1,20 @@
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
where
c.address_id in (
? , ?
)
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BIGINT] - [2]

View File

@ -0,0 +1,289 @@
/*
* 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.util.Arrays;
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.NoResultException;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
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.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;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
public class QueryAuditTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Customer.class,
Address.class
};
}
@Override
protected void addConfigOptions(Map options) {
options.put(
EnversSettings.AUDIT_STRATEGY,
ValidityAuditStrategy.class.getName()
);
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Address address = new Address();
address.setId( 1L );
address.setCountry( "România" );
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::entities-at-revision-example[]
Customer customer = (Customer) AuditReaderFactory.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, revisions.get( 0 ) )
.getSingleResult();
assertEquals("Doe", customer.getLastName());
//end::entities-at-revision-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::entities-filtering-example[]
List<Customer> customers = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.property( "firstName" ).eq( "John" ) )
.getResultList();
assertEquals(2, customers.size());
assertEquals( "Doe", customers.get( 0 ).getLastName() );
assertEquals( "Doe Jr.", customers.get( 1 ).getLastName() );
//end::entities-filtering-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::entities-filtering-by-entity-example[]
Address address = entityManager.getReference( Address.class, 1L );
List<Customer> customers = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.property( "address" ).eq( address ) )
.getResultList();
assertEquals(2, customers.size());
//end::entities-filtering-by-entity-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::entities-filtering-by-entity-identifier-example[]
List<Customer> customers = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.relatedId( "address" ).eq( 1L ) )
.getResultList();
assertEquals(2, customers.size());
//end::entities-filtering-by-entity-identifier-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::entities-in-clause-filtering-by-entity-identifier-example[]
List<Customer> customers = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.add( AuditEntity.relatedId( "address" ).in( new Object[] { 1L, 2L } ) )
.getResultList();
assertEquals(2, customers.size());
//end::entities-in-clause-filtering-by-entity-identifier-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::entities-filtering-and-pagination[]
List<Customer> customers = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, true, true )
.addOrder( AuditEntity.property( "lastName" ).desc() )
.add( AuditEntity.relatedId( "address" ).eq( 1L ) )
.setFirstResult( 1 )
.setMaxResults( 2 )
.getResultList();
assertEquals(1, customers.size());
//end::entities-filtering-and-pagination[]
} );
}
@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;
private String country;
private String city;
private String street;
private String streetNumber;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCountry() {
return country;
}
public void setCountry(String 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;
}
}
}