HHH-11886 - Elaborate Envers documentation and switch to actual source code examples
Refactor section about queries
This commit is contained in:
parent
6ad8302a3c
commit
12616d44e2
|
@ -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:
|
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.
|
horizontal:: 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.
|
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.
|
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.
|
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.
|
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]]
|
[[entities-at-revision]]
|
||||||
=== Querying for entities of a class at a given revision
|
=== Querying for entities of a class at a given revision
|
||||||
|
|
||||||
The entry point for this type of queries is:
|
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()
|
include::{sourcedir}/QueryAuditTest.java[tags=entities-at-revision-example]
|
||||||
.createQuery()
|
----
|
||||||
.forEntitiesAtRevision( MyEntity.class, revisionNumber );
|
====
|
||||||
|
|
||||||
|
[[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.
|
[source, SQL, indent=0]
|
||||||
For example, to select only entities where the "name" property is equal to "John":
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
----
|
||||||
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, SQL, indent=0]
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
----
|
||||||
query.add( AuditEntity.property( "address" ).eq( relatedEntityInstance ) );
|
include::{extrasdir}/entities-in-clause-filtering-by-entity-identifier-example.sql[]
|
||||||
// or
|
|
||||||
query.add( AuditEntity.relatedId( "address" ).eq( relatedEntityId ) );
|
|
||||||
// or
|
|
||||||
query.add( AuditEntity.relatedId( "address" ).in( relatedEntityId1, relatedEntityId2 ) );
|
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
You can limit the number of results, order them, and set aggregations and projections (except grouping) in the usual way.
|
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.
|
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:
|
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()
|
include::{sourcedir}/QueryAuditTest.java[tags=entities-filtering-and-pagination]
|
||||||
.forEntitiesAtRevision( Person.class, 12 )
|
|
||||||
.addOrder( AuditEntity.property( "surname" ).desc() )
|
|
||||||
.add( AuditEntity.relatedId( "address" ).eq( addressId ) )
|
|
||||||
.setFirstResult( 4 )
|
|
||||||
.setMaxResults( 2 )
|
|
||||||
.getResultList();
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[source, SQL, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/entities-filtering-and-pagination.sql[]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[revisions-of-entity]]
|
[[revisions-of-entity]]
|
||||||
=== Querying for revisions, at which entities of a given class changed
|
=== Querying for revisions, at which entities of a given class changed
|
||||||
|
|
||||||
|
|
|
@ -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 ?
|
|
@ -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]
|
|
@ -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]
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue