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