Hibernate.org Community Documentation

- Prev
- Next

## Chapter 4. Logging data for revisions

Envers provides an easy way to log additional data for each revision. You simply need to annotate one entity with `@RevisionEntity`, and a new instance of this entity will be persisted when a new revision is created (that is, whenever an audited entity is modified). As revisions are global, you can have at most one revisions entity.

Please note that the revision entity must be a mapped Hibernate entity.

This entity must have at least two properties:

1. an integer- or long-valued property, annotated with `@RevisionNumber`. Most often, this will be an auto-generated primary key.

2. a long- or j.u.Date- valued property, annotated with `@RevisionTimestamp`. Value of this property will be automatically set by Envers.

You can either add these properties to your entity, or extend `org.hibernate.envers.DefaultRevisionEntity`, which already has those two properties.

When using a `Date`, instead of a `long/Long` for the revision timestamp, take care not to use a mapping of the property which will loose precision (for example, using `@Temporal(DATE)` is wrong, as it doesn't store the time information, so many of your revisions will appear to happen at exactly the same time). A good choice is a `@Temporal(TIMESTAMP)`.

To fill the entity with additional data, you'll need to implement the `org.jboss.envers.RevisionListener` interface. Its newRevision method will be called when a new revision is created, before persisting the revision entity. The implementation should be stateless and thread-safe. The listener then has to be attached to the revisions entity by specifying it as a parameter to the `@RevisionEntity` annotation.

Alternatively, you can use the `getCurrentRevision` method of the `AuditReader` interface to obtain the current revision, and fill it with desired information. The method has a `persist` parameter specifying, if the revision entity should be persisted before returning. If set to `true`, the revision number will be available in the returned revision entity (as it is normally generated by the database), but the revision entity will be persisted regardless of wheter there are any audited entities changed. If set to `false`, the revision number will be `null`, but the revision entity will be persisted only if some audited entities have changed.

A simplest example of a revisions entity, which with each revision associates the username of the user making the change is:

    package org.jboss.envers.example;

    import org.hibernate.envers.RevisionEntity;
    import org.hibernate.envers.DefaultRevisionEntity;

    import javax.persistence.Entity;

    @Entity
    @RevisionEntity(ExampleListener.class)
    public class ExampleRevEntity extends DefaultRevisionEntity {
    	private String username;

    	public String getUsername() { return username; }
    	public void setUsername(String username) { this.username = username; }
    }

Or, if you don't want to extend any class:

    package org.hibernate.envers.example;

    import org.hibernate.envers.RevisionNumber;
    import org.hibernate.envers.RevisionTimestamp;
    import org.hibernate.envers.RevisionEntity;

    import javax.persistence.Id;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Entity;

    @Entity
    @RevisionEntity(ExampleListener.class)
    public class ExampleRevEntity {
        @Id
        @GeneratedValue
        @RevisionNumber
        private int id;

        @RevisionTimestamp
        private long timestamp;

        private String username;

        // Getters, setters, equals, hashCode ...
    }

An example listener, which, if used in a JBoss Seam application, stores the currently logged in user username:

    package org.hibernate.envers.example;

    import org.hibernate.envers.RevisionListener;
    import org.jboss.seam.security.Identity;
    import org.jboss.seam.Component;

    public class ExampleListener implements RevisionListener {
        public void newRevision(Object revisionEntity) {
            ExampleRevEntity exampleRevEntity = (ExampleRevEntity) revisionEntity;
            Identity identity = (Identity) Component.getInstance("org.jboss.seam.security.identity");

            exampleRevEntity.setUsername(identity.getUsername());
        }
    }

Having an "empty" revision entity - that is, with no additional properties except the two mandatory ones - is also an easy way to change the names of the table and of the properties in the revisions table automatically generated by Envers.

In case there is no entity annotated with `@RevisionEntity`, a default table will be generated, with the name `REVINFO`.

## 4.1. Tracking entity names modified during revisions

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. Users are allowed to implement custom mechanism of tracking modified entity names. 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 4.1. 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()

Copyright © 2004 Red Hat Inc.

- PrevChapter 3. Configuration
- Top of page
- Front page
- NextChapter 5. Queries