13.7. Envers
13.7.1. About Hibernate Envers
@Audited
, which store the history of changes made to the entity. The data can then be retrieved and queried.
- audit all mappings defined by the JPA specification,
- audit all hibernate mappings that extend the JPA specification,
- audit entities mapped by or using the native Hibernate API
- log data for each revision using a revision entity, and
- query historical data.
13.7.2. About Auditing Persistent Classes
@Audited
annotation. When the annotation is applied to a class, a table is created, which stores the revision history of the entity.
13.7.3. Auditing Strategies
13.7.3.1. About Auditing Strategies
- Default Audit Strategy
- This strategy persists the audit data together with a start revision. For each row that is inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, along with the start revision of its validity.Rows in the audit tables are never updated after insertion. Queries of audit information use subqueries to select the applicable rows in the audit tables, which are slow and difficult to index.
- Validity Audit Strategy
- This strategy stores the start revision, as well as the end revision of the audit information. For each row that is inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, along with the start revision of its validity.At the same time, the end revision field of the previous audit rows (if available) is set to this revision. Queries on the audit information can then use between start and end revision, instead of subqueries. This means that persisting audit information is a little slower because of the extra updates, but retrieving audit information is a lot faster.This can also be improved by adding extra indexes.
13.7.3.2. Set the Auditing Strategy
There are two audit strategies supported by JBoss EAP 6: the default and validity audit strategies. This task covers the steps required to define the auditing strategy for an application.
Procedure 13.6. Define a Auditing Strategy
- Configure the
org.hibernate.envers.audit_strategy
property in thepersistence.xml
file of the application. If the property is not set in thepersistence.xml
file, then the default audit strategy is used.Example 13.28. Set the Default Audit Strategy
<property name="org.hibernate.envers.audit_strategy" value="org.hibernate.envers.strategy.DefaultAuditStrategy"/>
Example 13.29. Set the Validity Audit Strategy
<property name="org.hibernate.envers.audit_strategy" value="org.hibernate.envers.strategy.ValidityAuditStrategy"/>
13.7.4. Getting Started with Entity Auditing
13.7.4.1. Add Auditing Support to a JPA Entity
Procedure 13.7. Add Auditing Support to a JPA Entity
- Configure the available auditing parameters to suit the deployment: Section 13.7.5.1, “Configure Envers Parameters”.
- Open the JPA entity to be audited.
- Import the
org.hibernate.envers.Audited
interface. - Apply the
@Audited
annotation to each field or property to be audited, or apply it once to the whole class.Example 13.30. Audit Two Fields
import org.hibernate.envers.Audited; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.GeneratedValue; import javax.persistence.Column; @Entity public class Person { @Id @GeneratedValue private int id; @Audited private String name; private String surname; @ManyToOne @Audited private Address address; // add getters, setters, constructors, equals and hashCode here }
Example 13.31. Audit an entire Class
import org.hibernate.envers.Audited; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.GeneratedValue; import javax.persistence.Column; @Entity @Audited public class Person { @Id @GeneratedValue private int id; private String name; private String surname; @ManyToOne private Address address; // add getters, setters, constructors, equals and hashCode here }
The JPA entity has been configured for auditing. A table called Entity_AUD
will be created to store the historical changes.
13.7.5. Configuration
13.7.5.1. Configure Envers Parameters
Procedure 13.8. Configure Envers Parameters
- Open the
persistence.xml
file for the application. - Add, remove or configure Envers properties as required. For a list of available properties, refer to Section 13.7.5.4, “Envers Configuration Properties”.
Example 13.32. Example Envers Parameters
<persistence-unit name="mypc"> <description>Persistence Unit.</description> <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.cache.use_second_level_cache" value="true" /> <property name="hibernate.cache.use_query_cache" value="true" /> <property name="hibernate.generate_statistics" value="true" /> <property name="org.hibernate.envers.versionsTableSuffix" value="_V" /> <property name="org.hibernate.envers.revisionFieldName" value="ver_rev" /> </properties> </persistence-unit>
- Result
- Auditing has been configured for all JPA entities in the application.
13.7.5.2. Enable or Disable Auditing at Runtime
This task covers the configuration steps required to enable/disable entity version auditing at runtime.
Procedure 13.9. Enable/Disable Auditing
- Subclass the
AuditEventListener
class. - Override the following methods that are called on Hibernate events:
- onPostInsert
- onPostUpdate
- onPostDelete
- onPreUpdateCollection
- onPreRemoveCollection
- onPostRecreateCollection
- Specify the subclass as the listener for the events.
- Determine if the change should be audited.
- Pass the call to the superclass if the change should be audited.
13.7.5.3. Configure Conditional Auditing
Hibernate Envers persists audit data in reaction to various Hibernate events, using a series of event listeners. These listeners are registered automatically if the Envers jar is in the class path. This task covers the steps required to implement conditional auditing, by overriding some of the Envers event listeners.
Procedure 13.10. Implement Conditional Auditing
- Set the
hibernate.listeners.envers.autoRegister
Hibernate property to false in thepersistence.xml
file. - Subclass each event listener to be overridden. Place the conditional auditing logic in the subclass, and call the super method if auditing should be performed.
- Create a custom implementation of
org.hibernate.integrator.spi.Integrator
, similar toorg.hibernate.envers.event.EnversIntegrator
. Use the event listener subclasses created in step two, rather than the default classes. - Add a
META-INF/services/org.hibernate.integrator.spi.Integrator
file to the jar. This file should contain the fully qualified name of the class implementing the interface.
Conditional auditing has been configured, overriding the default Envers event listeners.
13.7.5.4. Envers Configuration Properties
Property Name | Default Value | Description |
---|---|---|
org.hibernate.envers.audit_table_prefix
| |
A string that is prepended to the name of an audited entity, to create the name of the entity that will hold the audit information.
|
org.hibernate.envers.audit_table_suffix
|
_AUD
|
A string that is appended to the name of an audited entity to create the name of the entity that will hold the audit information. For example, if an entity with a table name of
Person is audited, Envers will generate a table called Person_AUD to store the historical data.
|
org.hibernate.envers.revision_field_name
|
REV
|
The name of the field in the audit entity that holds the revision number.
|
org.hibernate.envers.revision_type_field_name
|
REVTYPE
|
The name of the field in the audit entity that holds the type of revision. The current types of revisions possible are:
add , mod and del .
|
org.hibernate.envers.revision_on_collection_change
|
true
|
This property determines if a revision should be generated if a relation field that is not owned changes. This can either be a collection in a one-to-many relation, or the field using the
mappedBy attribute in a one-to-one relation.
|
org.hibernate.envers.do_not_audit_optimistic_locking_field
|
true
|
When true, properties used for optimistic locking (annotated with
@Version ) will automatically be excluded from auditing.
|
org.hibernate.envers.store_data_at_delete
|
false
|
This property defines whether or not entity data should be stored in the revision when the entity is deleted, instead of only the ID, with all other properties marked as null. This is not usually necessary, as the data is present in the last-but-one revision. Sometimes, however, it is easier and more efficient to access it in the last revision. However, this means the data the entity contained before deletion is stored twice.
|
org.hibernate.envers.default_schema
|
null (same as normal tables)
|
The default schema name used for audit tables. Can be overridden using the
@AuditTable(schema="...") annotation. If not present, the schema will be the same as the schema of the normal tables.
|
org.hibernate.envers.default_catalog
|
null (same as normal tables)
|
The default catalog name that should be used for audit tables. Can be overridden using the
@AuditTable(catalog="...") annotation. If not present, the catalog will be the same as the catalog of the normal tables.
|
org.hibernate.envers.audit_strategy
|
org.hibernate.envers.strategy.DefaultAuditStrategy
|
This property defines the audit strategy that should be used when persisting audit data. By default, only the revision where an entity was modified is stored. Alternatively,
org.hibernate.envers.strategy.ValidityAuditStrategy stores both the start revision and the end revision. Together, these define when an audit row was valid.
|
org.hibernate.envers.audit_strategy_validity_end_rev_field_name
|
REVEND
|
The column name that will hold the end revision number in audit entities. This property is only valid if the validity audit strategy is used.
|
org.hibernate.envers.audit_strategy_validity_store_revend_timestamp
|
false
|
This property defines whether the timestamp of the end revision, where the data was last valid, should be stored in addition to the end revision itself. This is useful to be able to purge old audit records out of a relational database by using table partitioning. Partitioning requires a column that exists within the table. This property is only evaluated if the
ValidityAuditStrategy is used.
|
org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name
|
REVEND_TSTMP
|
Column name of the timestamp of the end revision at which point the data was still valid. Only used if the
ValidityAuditStrategy is used, and org.hibernate.envers.audit_strategy_validity_store_revend_timestamp evaluates to true.
|
13.7.6. Queries
13.7.6.1. Retrieve Auditing Information
Hibernate Envers provides the functionality to retrieve audit information through queries. This topic provides examples of those queries.
Note
live
data, as they involve correlated subselects.
Example 13.33. Querying for Entities of a Class at a Given Revision
AuditQuery query = getAuditReader() .createQuery() .forEntitiesAtRevision(MyEntity.class, revisionNumber);
AuditEntity
factory class. The query below only selects entities where the name
property is equal to John
:
query.add(AuditEntity.property("name").eq("John"));
query.add(AuditEntity.property("address").eq(relatedEntityInstance)); // or query.add(AuditEntity.relatedId("address").eq(relatedEntityId));
List personsAtAddress = getAuditReader().createQuery() .forEntitiesAtRevision(Person.class, 12) .addOrder(AuditEntity.property("surname").desc()) .add(AuditEntity.relatedId("address").eq(addressId)) .setFirstResult(4) .setMaxResults(2) .getResultList();
Example 13.34. Query Revisions where Entities of a Given Class Changed
AuditQuery query = getAuditReader().createQuery() .forRevisionsOfEntity(MyEntity.class, false, true);
AuditEntity.revisionNumber()
- Specify constraints, projections and order on the revision number in which the audited entity was modified.
AuditEntity.revisionProperty(propertyName)
- Specify constraints, projections and order on a property of the revision entity, corresponding to the revision in which the audited entity was modified.
AuditEntity.revisionType()
- Provides accesses to the type of the revision (ADD, MOD, DEL).
MyEntity
class, with the entityId
ID has changed, after revision number 42:
Number revision = (Number) getAuditReader().createQuery() .forRevisionsOfEntity(MyEntity.class, false, true) .setProjection(AuditEntity.revisionNumber().min()) .add(AuditEntity.id().eq(entityId)) .add(AuditEntity.revisionNumber().gt(42)) .getSingleResult();
actualDate
for a given entity was larger than a given value, but as small as possible:
Number revision = (Number) getAuditReader().createQuery() .forRevisionsOfEntity(MyEntity.class, false, true) // We are only interested in the first revision .setProjection(AuditEntity.revisionNumber().min()) .add(AuditEntity.property("actualDate").minimize() .add(AuditEntity.property("actualDate").ge(givenDate)) .add(AuditEntity.id().eq(givenEntityId))) .getSingleResult();
minimize()
and maximize()
methods return a criteria, to which constraints can be added, which must be met by the entities with the maximized/minimized properties.
selectEntitiesOnly
- This parameter is only valid when an explicit projection is not set.If true, the result of the query will be a list of entities that changed at revisions satisfying the specified constraints.If false, the result will be a list of three element arrays. The first element will be the changed entity instance. The second will be an entity containing revision data. If no custom entity is used, this will be an instance of
DefaultRevisionEntity
. The third element array will be the type of the revision (ADD, MOD, DEL). selectDeletedEntities
- This parameter specifies if revisions in which the entity was deleted must be included in the results. If true, the entities will have the revision type
DEL
, and all fields, except id, will have the valuenull
.
Example 13.35. Query Revisions of an Entity that Modified a Given Property
MyEntity
with a given id, where the actualDate
property has been changed.
AuditQuery query = getAuditReader().createQuery() .forRevisionsOfEntity(MyEntity.class, false, true) .add(AuditEntity.id().eq(id)); .add(AuditEntity.property("actualDate").hasChanged())
hasChanged
condition can be combined with additional criteria. The query below will return a horizontal slice for MyEntity
at the time the revisionNumber was generated. It will be limited to the revisions that modified prop1
, but not prop2
.
AuditQuery query = getAuditReader().createQuery() .forEntitiesAtRevision(MyEntity.class, revisionNumber) .add(AuditEntity.property("prop1").hasChanged()) .add(AuditEntity.property("prop2").hasNotChanged());
MyEntities
changed in revisionNumber with prop1
modified and prop2
untouched."
forEntitiesModifiedAtRevision
query:
AuditQuery query = getAuditReader().createQuery() .forEntitiesModifiedAtRevision(MyEntity.class, revisionNumber) .add(AuditEntity.property("prop1").hasChanged()) .add(AuditEntity.property("prop2").hasNotChanged());
Example 13.36. Query Entities Modified in a Given Revision
Set<Pair<String, Class>> modifiedEntityTypes = getAuditReader() .getCrossTypeRevisionChangesReader().findEntityTypes(revisionNumber);
org.hibernate.envers.CrossTypeRevisionChangesReader
:
List<Object> findEntities(Number)
- Returns snapshots of all audited entities changed (added, updated and removed) in a given revision. Executes
n+1
SQL queries, wheren
is a number of different entity classes modified within the specified revision. List<Object> findEntities(Number, RevisionType)
- Returns snapshots of all audited entities changed (added, updated or removed) in a given revision filtered by modification type. Executes
n+1
SQL queries, wheren
is a number of different entity classes modified within specified revision. Map<RevisionType, List<Object>> findEntitiesGroupByRevisionType(Number)
- Returns a map containing lists of entity snapshots grouped by modification operation (e.g. addition, update and removal). Executes
3n+1
SQL queries, wheren
is a number of different entity classes modified within specified revision.