Hibernate.orgCommunity Documentation

Chapter 19. Envers

Table of Contents

19.1. Basics
19.2. Configuration
19.3. Additional mapping annotations
19.4. Choosing an audit strategy
19.5. Revision Log
19.5.1. Tracking entity names modified during revisions
19.6. Tracking entity changes at property level
19.7. Queries
19.7.1. Querying for entities of a class at a given revision
19.7.2. Querying for revisions, at which entities of a given class changed
19.7.3. Querying for revisions of entity that modified given property
19.7.4. Querying for entities modified in a given revision
19.8. Conditional auditing
19.9. Understanding the Envers Schema
19.10. Generating schema with Ant
19.11. Mapping exceptions
19.11.1. What isn't and will not be supported
19.11.2. What isn't and will be supported
19.11.3. @OneToMany+@JoinColumn
19.12. Advanced: Audit table partitioning
19.12.1. Benefits of audit table partitioning
19.12.2. Suitable columns for audit table partitioning
19.12.3. Audit table partitioning example
19.13. Envers links

To audit changes that are performed on an entity, you only need two things: the hibernate-envers jar on the classpath and an @Audited annotation on the entity.

Important

Unlike in previous versions, you no longer need to specify listeners in the Hibernate configuration file. Just putting the Envers jar on the classpath is enough - listeners will be registered automatically.

And that's all - you can create, modify and delete the entities as always. If you look at the generated schema for your entities, or at the data persisted by Hibernate, you will notice that there are no changes. However, for each audited entity, a new table is introduced - entity_table_AUD, which stores the historical data, whenever you commit a transaction. Envers automatically creates audit tables if hibernate.hbm2ddl.auto option is set to create, create-drop or update. Otherwise, to export complete database schema programatically, use org.hibernate.envers.tools.hbm2ddl.EnversSchemaGenerator. Appropriate DDL statements can be also generated with Ant task described later in this manual.

Instead of annotating the whole class and auditing all properties, you can annotate only some persistent properties with @Audited. This will cause only these properties to be audited.

The audit (history) of an entity can be accessed using the AuditReader interface, which can be obtained having an open EntityManager or Session via the AuditReaderFactory. See the javadocs for these classes for details on the functionality offered.

It is possible to configure various aspects of Hibernate Envers behavior, such as table names, etc.

Table 19.1. Envers Configuration Properties

Property nameDefault valueDescription
org.hibernate.envers.audit_table_prefix String that will be prepended to the name of an audited entity to create the name of the entity, that will hold audit information.
org.hibernate.envers.audit_table_suffix _AUD String that will be appended to the name of an audited entity to create the name of the entity, that will hold audit information. If you audit an entity with a table name Person, in the default setting Envers will generate a Person_AUD table to store historical data.
org.hibernate.envers.revision_field_name REV Name of a field in the audit entity that will hold the revision number.
org.hibernate.envers.revision_type_field_name REVTYPE Name of a field in the audit entity that will hold the type of the revision (currently, this can be: add, mod, del).
org.hibernate.envers.revision_on_collection_change true Should a revision be generated when a not-owned relation field changes (this can be either a collection in a one-to-many relation, or the field using "mappedBy" attribute in a one-to-one relation).
org.hibernate.envers.do_not_audit_optimistic_locking_field true When true, properties to be used for optimistic locking, annotated with @Version, will be automatically not audited (their history won't be stored; it normally doesn't make sense to store it).
org.hibernate.envers.store_data_at_delete false Should the entity data be stored in the revision when the entity is deleted (instead of only storing the id and all other properties as null). This is not normally needed, 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 (then the data that the entity contained before deletion is stored twice).
org.hibernate.envers.default_schema null (same schema as table being audited) The default schema name that should be 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 table being audited.
org.hibernate.envers.default_catalog null (same catalog as table being audited) 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 The audit strategy that should be used when persisting audit data. The default stores only the revision, at which an entity was modified. An alternative, the org.hibernate.envers.strategy.ValidityAuditStrategy stores both the start revision and the end revision. Together these define when an audit row was valid, hence the name ValidityAuditStrategy.
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 Should the timestamp of the end revision be stored, until which the data was valid, 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 until which the data was valid. Only used if the ValidityAuditStrategy is used, and org.hibernate.envers.audit_strategy_validity_store_revend_timestamp evaluates to true
org.hibernate.envers.use_revision_entity_with_native_id true Boolean flag that determines the strategy of revision number generation. Default implementation of revision entity uses native identifier generator. If current database engine does not support identity columns, users are advised to set this property to false. In this case revision numbers are created by preconfigured org.hibernate.id.enhanced.SequenceStyleGenerator. See:
  1. org.hibernate.envers.DefaultRevisionEntity
  2. org.hibernate.envers.enhanced.SequenceIdRevisionEntity
org.hibernate.envers.track_entities_changed_in_revision false Should entity types, that have been modified during each revision, be tracked. The default implementation creates REVCHANGES table that stores entity names of modified persistent objects. Single record encapsulates the revision identifier (foreign key to REVINFO table) and a string value. For more information refer to Section 19.5.1, “Tracking entity names modified during revisions” and Section 19.7.4, “Querying for entities modified in a given revision”.
org.hibernate.envers.global_with_modified_flag false, can be individually overriden with @Audited(withModifiedFlag=true) Should property modification flags be stored for all audited entities and all properties. When set to true, for all properties an additional boolean column in the audit tables will be created, filled with information if the given property changed in the given revision. When set to false, such column can be added to selected entities or properties using the @Audited annotation. For more information refer to Section 19.6, “Tracking entity changes at property level” and Section 19.7.3, “Querying for revisions of entity that modified given property”.
org.hibernate.envers.modified_flag_suffix _MOD The suffix for columns storing "Modified Flags". For example: a property called "age", will by default get modified flag with column name "age_MOD".
org.hibernate.envers.embeddable_set_ordinal_field_name SETORDINAL Name of column used for storing ordinal of the change in sets of embeddable elements.
org.hibernate.envers.cascade_delete_revision false While deleting revision entry, remove data of associated audited entities. Requires database support for cascade row removal.
org.hibernate.envers.allow_identifier_reuse false Guarantees proper validity audit strategy behavior when application reuses identifiers of deleted entities. Exactly one row with null end date exists for each identifier.

Important

The following configuration options have been added recently and should be regarded as experimental:

  1. org.hibernate.envers.track_entities_changed_in_revision
  2. org.hibernate.envers.using_modified_flag
  3. org.hibernate.envers.modified_flag_suffix

The name of the audit table can be set on a per-entity basis, using the @AuditTable annotation. It may be tedious to add this annotation to every audited entity, so if possible, it's better to use a prefix/suffix.

If you have a mapping with secondary tables, audit tables for them will be generated in the same way (by adding the prefix and suffix). If you wish to overwrite this behaviour, you can use the @SecondaryAuditTable and @SecondaryAuditTables annotations.

If you'd like to override auditing behaviour of some fields/properties inherited from @Mappedsuperclass or in an embedded component, you can apply the @AuditOverride(s) annotation on the subtype or usage site of the component.

If you want to audit a relation mapped with @OneToMany+@JoinColumn, please see Section 19.11, “Mapping exceptions” for a description of the additional @AuditJoinTable annotation that you'll probably want to use.

If you want to audit a relation, where the target entity is not audited (that is the case for example with dictionary-like entities, which don't change and don't have to be audited), just annotate it with @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Then, while reading historic versions of your entity, the relation will always point to the "current" related entity. By default Envers throws javax.persistence.EntityNotFoundException when "current" entity does not exist in the database. Apply @NotFound(action = NotFoundAction.IGNORE) annotation to silence the exception and assign null value instead. Hereby solution causes implicit eager loading of to-one relations.

If you'd like to audit properties of a superclass of an entity, which are not explicitly audited (which don't have the @Audited annotation on any properties or on the class), you can list the superclasses in the auditParents attribute of the @Audited annotation. Please note that auditParents feature has been deprecated. Use @AuditOverride(forClass = SomeEntity.class, isAudited = true/false) instead.

After the basic configuration it is important to choose the audit strategy that will be used to persist and retrieve audit information. There is a trade-off between the performance of persisting and the performance of querying the audit information. Currently there two audit strategies.

  1. The default audit strategy persists the audit data together with a start revision. For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together 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. These subqueries are notoriously slow and difficult to index.

  2. The alternative is a validity audit strategy. This strategy stores the start-revision and the end-revision of audit information. For each row inserted, updated or deleted in an audited table, one or more rows are inserted in the audit tables, together with the start revision of its validity. But at the same time the end-revision field of the previous audit rows (if available) are set to this revision. Queries on the audit information can then use 'between start and end revision' instead of subqueries as used by the default audit strategy.

    The consequence of this strategy is that persisting audit information will be a bit slower, because of the extra updates involved, but retrieving audit information will be a lot faster. This can be improved by adding extra indexes.

When Envers starts a new revision, it creates a new revision entity which stores information about the revision. By default, that includes just

  1. revision number - An integral value (int/Integer or long/Long). Essentially the primary key of the revision

  2. revision timestamp - either a long/Long or java.util.Date value representing the instant at which the revision was made. When using a java.util.Date, instead of a long/Long for the revision timestamp, take care not to store it to a column data type which will loose precision.

Envers handles this information as an entity. By default it uses its own internal class to act as the entity, mapped to the REVINFO table. You can, however, supply your own approach to collecting this information which might be useful to capture additional details such as who made a change or the ip address from which the request came. There are 2 things you need to make this work.

  1. First, you will need to tell Envers about the entity you wish to use. Your entity must use the @org.hibernate.envers.RevisionEntity annotation. It must define the 2 attributes described above annotated with @org.hibernate.envers.RevisionNumber and @org.hibernate.envers.RevisionTimestamp, respectively. You can extend from org.hibernate.envers.DefaultRevisionEntity, if you wish, to inherit all these required behaviors.

    Simply add the custom revision entity as you do your normal entities. Envers will "find it". Note that it is an error for there to be multiple entities marked as @org.hibernate.envers.RevisionEntity

  2. Second, you need to tell Envers how to create instances of your revision entity which is handled by the newRevision method of the org.jboss.envers.RevisionListener interface.

    You tell Envers your custom org.hibernate.envers.RevisionListener implementation to use by specifying it on the @org.hibernate.envers.RevisionEntity annotation, using the value attribute. If your RevisionListener class is inaccessible from @RevisionEntity (e.g. exists in a different module), set org.hibernate.envers.revision_listener property to it's fully qualified name. Class name defined by the configuration parameter overrides revision entity's value attribute.

@Entity
@RevisionEntity( MyCustomRevisionListener.class )
public class MyCustomRevisionEntity {
    ...
}

public class MyCustomRevisionListener implements RevisionListener {
    public void newRevision(Object revisionEntity) {
        ( (MyCustomRevisionEntity) revisionEntity )...;
    }
}

An alternative method to using the org.hibernate.envers.RevisionListener is to instead call the getCurrentRevision method of the org.hibernate.envers.AuditReader interface to obtain the current revision, and fill it with desired information. The method accepts a persist parameter indicating whether the revision entity should be persisted prior to returning from this method. true ensures that the returned entity has access to its identifier value (revision number), but the revision entity will be persisted regardless of whether there are any audited entities changed. false means that the revision number will be null, but the revision entity will be persisted only if some audited entities have changed.


By default entity types that have been changed in each revision are not being tracked. This implies the necessity to query all tables storing audited data in order to retrieve changes made during specified revision. Envers provides a simple mechanism that creates REVCHANGES table which stores entity names of modified persistent objects. Single record encapsulates the revision identifier (foreign key to REVINFO table) and a string value.

Tracking of modified entity names can be enabled in three different ways:

  1. Set org.hibernate.envers.track_entities_changed_in_revision parameter to true. In this case org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity will be implicitly used as the revision log entity.

  2. Create a custom revision entity that extends org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity class.

    @Entity
    @RevisionEntity
    public class ExtendedRevisionEntity
                 extends DefaultTrackingModifiedEntitiesRevisionEntity {
        ...
    }
  3. Mark an appropriate field of a custom revision entity with @org.hibernate.envers.ModifiedEntityNames annotation. The property is required to be of Set<String> type.

    @Entity
    @RevisionEntity
    public class AnnotatedTrackingRevisionEntity {
        ...
    
        @ElementCollection
        @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV"))
        @Column(name = "ENTITYNAME")
        @ModifiedEntityNames
        private Set<String> modifiedEntityNames;
        
        ...
    }

Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a specified revision by utilizing API described in Section 19.7.4, “Querying for entities modified in a given revision”.

Users are also allowed to implement custom mechanism of tracking modified entity types. In this case, they shall pass their own implementation of org.hibernate.envers.EntityTrackingRevisionListener interface as the value of @org.hibernate.envers.RevisionEntity annotation. EntityTrackingRevisionListener interface exposes one method that notifies whenever audited entity instance has been added, modified or removed within current revision boundaries.

Example 19.2. Custom implementation of tracking entity classes modified during revisions

                    CustomEntityTrackingRevisionListener.java

public class CustomEntityTrackingRevisionListener
             implements EntityTrackingRevisionListener {
    @Override
    public void entityChanged(Class entityClass, String entityName,
                              Serializable entityId, RevisionType revisionType,
                              Object revisionEntity) {
        String type = entityClass.getName();
        ((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityType(type);
    }

    @Override
    public void newRevision(Object revisionEntity) {
    }
}
                    CustomTrackingRevisionEntity.java

@Entity
@RevisionEntity(CustomEntityTrackingRevisionListener.class)
public class CustomTrackingRevisionEntity {
    @Id
    @GeneratedValue
    @RevisionNumber
    private int customId;

    @RevisionTimestamp
    private long customTimestamp;

    @OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
    private Set<ModifiedEntityTypeEntity> modifiedEntityTypes =
                                              new HashSet<ModifiedEntityTypeEntity>();
    
    public void addModifiedEntityType(String entityClassName) {
        modifiedEntityTypes.add(new ModifiedEntityTypeEntity(this, entityClassName));
    }
    
    ...
}
                    ModifiedEntityTypeEntity.java

@Entity
public class ModifiedEntityTypeEntity {
    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    private CustomTrackingRevisionEntity revision;
    
    private String entityClassName;
    
    ...
}
CustomTrackingRevisionEntity revEntity =
    getAuditReader().findRevision(CustomTrackingRevisionEntity.class, revisionNumber);
Set<ModifiedEntityTypeEntity> modifiedEntityTypes = revEntity.getModifiedEntityTypes()

By default the only information stored by Envers are revisions of modified entities. This approach lets user create audit queries based on historical values of entity's properties. Sometimes it is useful to store additional metadata for each revision, when you are interested also in the type of changes, not only about the resulting values. The feature described in Section 19.5.1, “Tracking entity names modified during revisions” makes it possible to tell which entities were modified in given revision. Feature described here takes it one step further. "Modification Flags" enable Envers to track which properties of audited entities were modified in a given revision.

Tracking entity changes at property level can be enabled by:

  1. setting org.hibernate.envers.global_with_modified_flag configuration property to true. This global switch will cause adding modification flags for all audited properties in all audited entities.

  2. using @Audited(withModifiedFlag=true) on a property or on an entity.

The trade-off coming with this functionality is an increased size of audit tables and a very little, almost negligible, performance drop during audit writes. This is due to the fact that every tracked property has to have an accompanying boolean column in the schema that stores information about the property's modifications. Of course it is Envers' job to fill these columns accordingly - no additional work by the developer is required. Because of costs mentioned, it is recommended to enable the feature selectively, when needed with use of the granular configuration means described above.

To see how "Modified Flags" can be utilized, check out the very simple query API that uses them: Section 19.7.3, “Querying for revisions of entity that modified given property”.

You can think of historic data as having two dimension. The first - horizontal - is the state of the database at a given revision. Thus, you can query for entities as they were at revision N. The second - vertical - are 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 main limitation of the current queries implementation is that you cannot traverse relations. 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.

In the future, queries will be 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 Section 19.2, “Configuration”.

The entry point for this type of queries is:

AuditQuery query = getAuditReader().createQuery()
    .forRevisionsOfEntity(MyEntity.class, false, true);

You can add constraints to this query in the same way as to the previous one. There are some additional possibilities:

  1. using AuditEntity.revisionNumber() you can specify constraints, projections and order on the revision number, in which the audited entity was modified

  2. similarly, using AuditEntity.revisionProperty(propertyName) you can specify constraints, projections and order on a property of the revision entity, corresponding to the revision in which the audited entity was modified

  3. AuditEntity.revisionType() gives you access as above to the type of the revision (ADD, MOD, DEL).

Using these methods, you can order the query results by revision number, set projection or constraint the revision number to be greater or less than a specified value, etc. For example, the following query will select the smallest revision number, at which entity of class MyEntity with id entityId 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();

The second additional feature you can use in queries for revisions is the ability to maximalize/minimize a property. For example, if you want to select the revision, at which the value of the actualDate for a given entity was larger then 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();

The minimize() and maximize() methods return a criteria, to which you can add constraints, which must be met by the entities with the maximized/minimized properties. AggregatedAuditExpression#computeAggregationInInstanceContext() enables the possibility to compute aggregated expression in the context of each entity instance separately. It turns out useful when querying for latest revisions of all entities of a particular type.

You probably also noticed that there are two boolean parameters, passed when creating the query. The first one, selectEntitiesOnly, is only valid when you don't set an explicit projection. If true, the result of the query will be a list of entities (which 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 will be the type of the revision (one of the values of the RevisionType enumeration: ADD, MOD, DEL).

The second parameter, selectDeletedEntities, specifies if revisions, in which the entity was deleted should be included in the results. If yes, such entities will have the revision type DEL and all fields, except the id, null.

For the two types of queries described above it's possible to use special Audit criteria called hasChanged() and hasNotChanged() that makes use of the functionality described in Section 19.6, “Tracking entity changes at property level”. They're best suited for vertical queries, however existing API doesn't restrict their usage for horizontal ones. Let's have a look at following examples:

AuditQuery query = getAuditReader().createQuery()
    .forRevisionsOfEntity(MyEntity.class, false, true)
    .add(AuditEntity.id().eq(id));
    .add(AuditEntity.property("actualDate").hasChanged())
            

This query will return all revisions of MyEntity with given id, where the actualDate property has been changed. Using this query we won't get all other revisions in which actualDate wasn't touched. Of course nothing prevents user from combining hasChanged condition with some additional criteria - add method can be used here in a normal way.

AuditQuery query = getAuditReader().createQuery()
    .forEntitiesAtRevision(MyEntity.class, revisionNumber)
    .add(AuditEntity.property("prop1").hasChanged())
    .add(AuditEntity.property("prop2").hasNotChanged());
            

This query will return horizontal slice for MyEntity at the time revisionNumber was generated. It will be limited to revisions that modified prop1 but not prop2. Note that the result set will usually also contain revisions with numbers lower than the revisionNumber, so we cannot read this query as "Give me all MyEntities changed in revisionNumber with prop1 modified and prop2 untouched". To get such result we have to use the forEntitiesModifiedAtRevision query:

AuditQuery query = getAuditReader().createQuery()
    .forEntitiesModifiedAtRevision(MyEntity.class, revisionNumber)
    .add(AuditEntity.property("prop1").hasChanged())
    .add(AuditEntity.property("prop2").hasNotChanged());
            

Envers persists audit data in reaction to various Hibernate events (e.g. post update, post insert, and so on), using a series of even listeners from the org.hibernate.envers.event.spi package. By default, if the Envers jar is in the classpath, the event listeners are auto-registered with Hibernate.

Conditional auditing can be implemented by overriding some of the Envers event listeners. To use customized Envers event listeners, the following steps are needed:

  1. Turn off automatic Envers event listeners registration by setting the hibernate.listeners.envers.autoRegister Hibernate property to false.

  2. Create subclasses for appropriate event listeners. For example, if you want to conditionally audit entity insertions, extend the org.hibernate.envers.event.spi.EnversPostInsertEventListenerImpl class. Place the conditional-auditing logic in the subclasses, call the super method if auditing should be performed.

  3. Create your own implementation of org.hibernate.integrator.spi.Integrator, similar to org.hibernate.envers.boot.internal.EnversIntegrator. Use your event listener classes instead of the default ones.

  4. For the integrator to be automatically used when Hibernate starts up, you will need to add a META-INF/services/org.hibernate.integrator.spi.Integrator file to your jar. The file should contain the fully qualified name of the class implementing the interface.

For each audited entity (that is, for each entity containing at least one audited field), an audit table is created. By default, the audit table's name is created by adding a "_AUD" suffix to the original table name, but this can be overridden by specifying a different suffix/prefix in the configuration or per-entity using the @org.hibernate.envers.AuditTable annotation.

Audit table columns

  1. id of the original entity (this can be more then one column in the case of composite primary keys)

  2. revision number - an integer. Matches to the revision number in the revision entity table.

  3. revision type - a small integer

  4. audited fields from the original entity

The primary key of the audit table is the combination of the original id of the entity and the revision number - there can be at most one historic entry for a given entity instance at a given revision.

The current entity data is stored in the original table and in the audit table. This is a duplication of data, however as this solution makes the query system much more powerful, and as memory is cheap, hopefully this won't be a major drawback for the users. A row in the audit table with entity id ID, revision N and data D means: entity with id ID has data D from revision N upwards. Hence, if we want to find an entity at revision M, we have to search for a row in the audit table, which has the revision number smaller or equal to M, but as large as possible. If no such row is found, or a row with a "deleted" marker is found, it means that the entity didn't exist at that revision.

The "revision type" field can currently have three values: 0, 1, 2, which means ADD, MOD and DEL, respectively. A row with a revision of type DEL will only contain the id of the entity and no data (all fields NULL), as it only serves as a marker saying "this entity was deleted at that revision".

Additionally, there is a revision entity table which contains the information about the global revision. By default the generated table is named REVINFO and contains just 2 columns: ID and TIMESTAMP. A row is inserted into this table on each new revision, that is, on each commit of a transaction, which changes audited data. The name of this table can be configured, the name of its columns as well as adding additional columns can be achieved as discussed in Section 19.5, “Revision Log”.

While global revisions are a good way to provide correct auditing of relations, some people have pointed out that this may be a bottleneck in systems, where data is very often modified. One viable solution is to introduce an option to have an entity "locally revisioned", that is revisions would be created for it independently. This wouldn't enable correct versioning of relations, but wouldn't also require the REVINFO table. Another possibility is to introduce a notion of "revisioning groups": groups of entities which share revision numbering. Each such group would have to consist of one or more strongly connected component of the graph induced by relations between entities. Your opinions on the subject are very welcome on the forum! :)

If you'd like to generate the database schema file with the Hibernate Tools Ant task, you'll probably notice that the generated file doesn't contain definitions of audit tables. To generate also the audit tables, you simply need to use org.hibernate.tool.ant.EnversHibernateToolTask instead of the usual org.hibernate.tool.ant.HibernateToolTask. The former class extends the latter, and only adds generation of the version entities. So you can use the task just as you used to.

For example:

<target name="schemaexport" depends="build-demo"
  description="Exports a generated schema to DB and file">
  <taskdef name="hibernatetool"
    classname="org.hibernate.tool.ant.EnversHibernateToolTask"
    classpathref="build.demo.classpath"/>

  <hibernatetool destdir=".">
    <classpath>
      <fileset refid="lib.hibernate" />
      <path location="${build.demo.dir}" />
      <path location="${build.main.dir}" />
    </classpath>
    <jpaconfiguration persistenceunit="ConsolePU" />
    <hbm2ddl
      drop="false"
      create="true"
      export="false"
      outputfilename="versioning-ddl.sql"
      delimiter=";"
      format="true"/>
  </hibernatetool>
</target>

Will generate the following schema:

    create table Address (
        id integer generated by default as identity (start with 1),
        flatNumber integer,
        houseNumber integer,
        streetName varchar(255),
        primary key (id)
    );

    create table Address_AUD (
        id integer not null,
        REV integer not null,
        flatNumber integer,
        houseNumber integer,
        streetName varchar(255),
        REVTYPE tinyint,
        primary key (id, REV)
    );

    create table Person (
        id integer generated by default as identity (start with 1),
        name varchar(255),
        surname varchar(255),
        address_id integer,
        primary key (id)
    );

    create table Person_AUD (
        id integer not null,
        REV integer not null,
        name varchar(255),
        surname varchar(255),
        REVTYPE tinyint,
        address_id integer,
        primary key (id, REV)
    );

    create table REVINFO (
        REV integer generated by default as identity (start with 1),
        REVTSTMP bigint,
        primary key (REV)
    );

    alter table Person
        add constraint FK8E488775E4C3EA63
        foreign key (address_id)
        references Address;
    

Generally SQL tables must be partitioned on a column that exists within the table. As a rule it makes sense to use either the end revision or the end revision timestamp column for partioning of audit tables.

The reason why the end revision information should be used for audit table partioning is based on the assumption that audit tables should be partionioned on an 'increasing level of interestingness', like so:

  1. A couple of partitions with audit data that is not very (or no longer) interesting. This can be stored on slow media, and perhaps even be purged eventually.

  2. Some partitions for audit data that is potentially interesting.

  3. One partition for audit data that is most likely to be interesting. This should be stored on the fastest media, both for reading and writing.

In order to determine a suitable column for the 'increasing level of interestingness', consider a simplified example of a salary registration for an unnamed agency.

Currently, the salary table contains the following rows for a certain person X:


The salary for the current fiscal year (2010) is unknown. The agency requires that all changes in registered salaries for a fiscal year are recorded (i.e. an audit trail). The rationale behind this is that decisions made at a certain date are based on the registered salary at that time. And at any time it must be possible reproduce the reason why a certain decision was made at a certain date.

The following audit information is available, sorted on in order of occurrence:


  1. Hibernate main page

  2. Forum

  3. JIRA issue tracker (when adding issues concerning Envers, be sure to select the "envers" component!)

  4. IRC channel

  5. Envers Blog

  6. FAQ