In the previous recipe, we used the versioning feature of hibernate to check how many times a particular record has been modified. This is a good feature; however, it gives us just a number. Versioning does not store the modified data anywhere. So, it's hard to find out the previous state of the object before the modification.
As a solution to this, hibernate provides another project called Envers.
Envers helps us maintain the history of the database and it keeps track of the modifications in the database table rows. For this to work, we have to change the configuration in the POJO and configuration (.cfg.xml
) files.
To configure Envers with hibernate, we need JAR files in our project. You can use the following Maven dependency for the Maven-based project:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>4.3.10.Final</version> </dependency>
Once we configure Envers in our application, it creates a version table that contains the fields of the original table. Whenever the original table gets modified, hibernate automatically adds an entry in the version table; so, for every insert, update, and delete operation, hibernate inserts the records in the version table. Another table is automatically created by hibernate with the name revinfo
that stores revision information such as the revision id and revision timestamp.
Here, we will download the required libraries using the Maven dependency. The following code shows how to create the required classes and tables.
The following code shows an Employee
POJO and the changes in the configuration file (*.cgf.xml
):
Source file: Employee.java
@Entity @Table(name = "employee") /* Line 3 */ @Audited public class Employee { @Id @GeneratedValue private long id; @Column(name = "name") private String name; /* Line 13 */ @NotAudited @Column(name="password") private String password; // getters ans setters }
Source file: hibernate.cfg.xml
Add the below lines to your configuration file:
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-insert"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-update"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-delete"/>
Use the following table script if the hibernate.hbm2ddl.auto
configuration property is not set to create
:
Use the following script to create the employee
table:
CREATE TABLE `employee` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) );
Use the following script to create the employee_aud
table:
CREATE TABLE `employee_aud` ( `id` bigint(20) NOT NULL, `REV` int(11) NOT NULL, `REVTYPE` tinyint(4) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`,`REV`), KEY `FK_REVISION_ID` (`REV`), CONSTRAINT `FK_REVISION_ID` FOREIGN KEY (`REV`) REFERENCES `revinfo` (`REV`) );
Use the following script to create the revinfo
table:
CREATE TABLE `revinfo` ( `REV` int(11) NOT NULL AUTO_INCREMENT, `REVTSTMP` bigint(20) DEFAULT NULL, PRIMARY KEY (`REV`) );
Now, we will insert a record in the employee
table and take a look at the tables created by hibernate with the data. Update the following code:
Session session = sessionFactory.openSession(); Transaction transaction = session.getTransaction(); transaction.begin(); Employee employee = new Employee(); employee.setName("Aarush"); employee.setPassword("p@$sw0rd"); session.save(employee); transaction.commit(); session.close();
/* Line 1 */ Hibernate: insert into employee (name, password) values (?, ?) /* Line 2 */ Hibernate: insert into REVINFO (REVTSTMP) values (?) /* Line 3 */ Hibernate: insert into employee_AUD (REVTYPE, name, id, REV) values (?, ?, ?, ?)
The following employee table shows the data after the insertion is completed:
id |
name |
password |
---|---|---|
|
|
|
The following is the database table structure for the REVINFO
table:
REV |
REVTSTMP |
---|---|
|
|
The following is the database table structure for the employee_AUD
table:
id |
REV |
REVTYPE |
name |
---|---|---|---|
|
|
|
|
Now, we will discuss how this feature works. We will take a look at the changes in each file in detail.
Let's consider the Employee.java
file. In the Employee
class, we added the @Audited
annotation at the class level shown in Line 3
.
@Audited
is present at the class level. This means that hibernate will enable the history of the Employee
object and store the changes in the revision table.
Another useful annotation used in this class is @NotAudited
, which is shown in Line 13
.
Using the @Audited
annotation at the class level means that all the fields of that class are involved in the auditing process. If we do not want any field to be involved in the auditing process, the @NotAudited
annotation is used. For instance, here we annotate a password field with the @NotAudited
annotation, so hibernate will ignore this field during auditing.
Now, let's consider the hibernate.cfg.xml
file. In this configuration file, we added three new listener tags, where the class attribute defines the Listener
class and the type attribute defines a type of operation, such as post-insert
, post-update
, and post-delete
.
There are many events available in hibernate. Here, post-insert
means that the auditing is done after the insertion is completed. This works in a similar way for post-update
and post-delete
.
Once we execute the code, hibernate will create three tables:
employee
: This represents the Employee
class.employee_AUD
: This represents the audit table for the Employee
class. Hibernate will create an audit table by the concatenation of the actual table name as a prefix and the "_AUD"
value as a suffix if value is not provided.revinfo
: This stores the revision information, such as the revision id and revision timestamp.We can change the suffix and prefix value of the table as well as the audit table name in the following way:
@AuditTable(value="emp_history")
public class Employee {
// other fields and setters/getters
}
Now, hibernate will create the table name, emp_history
; the prefix and suffix are ignored in this case.
To change the audit table suffix, you can update the following configurations:
<property name="org.hibernate.envers.auditTableSuffix"> _history </property>
You can also use:
<property name="org.hibernate.envers.audit_table_suffix"> _history </property>
To change the audit table prefix, you can update the following configurations:
<property name="org.hibernate.envers.auditTablePrefix"> history_ </property>
You can also use:
<property name="org.hibernate.envers.audit_table_prefix"> history_ </property>
Now, hibernate will create an audit table with the given configuration, which will contain all auditable fields and the REV
and REVTYPE
column.
The REV
and REVTYPE
columns are used to maintain the revisions. To change the name of the REV
and REVTYPE
columns, use the following code:
To change the revision field name, you can update the following configurations:
<property name="org.hibernate.envers.revision_field_name"> REV_COL </property>
You can also use:
<property name="org.hibernate.envers.revisionFieldName"> REV_COL </property>