Recently, I wanted to keep the change history of entities saved via Hibernate and also have a versioned database. Hence, I used Hibernate Envers and Liquibase .
The tables for envers, both the revinfo table and the _AUD tables for each entity, have to be included in the changeset of Liquibase:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xmlns= "http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation= "http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd" >
<property name= "long_type" value= "bigint" dbms= "postgresql" />
<property name= "long_type" value= "long" dbms= "h2" />
<changeSet id= "CreateBasicSchema" author= "Steven Schwenke" >
<createSequence sequenceName= "hibernate_sequence" startValue= "0" incrementBy= "1" />
<createTable tableName= "revinfo" >
<column name= "rev" type= "integer" autoIncrement= "true" >
<constraints primaryKey= "true" />
</column>
<column name= "revtstmp" type= "bigint" />
</createTable>
<createTable tableName= "stuff" >
<column name= "id" type= "${long_type}" >
<constraints nullable= "false" unique= "true" primaryKey= "true" />
</column>
<column name= "text" type= "varchar(36)" >
<constraints nullable= "false" />
</column>
</createTable>
<createTable tableName= "stuff_aud" >
<column name= "id" type= "${long_type}" >
<constraints nullable= "false" />
</column>
<column name= "rev" type= "integer" >
<constraints referencedTableName= "revinfo"
foreignKeyName= "fk_brands_stuff_revinfo"
referencedColumnNames= "rev"
nullable= "false" />
</column>
<column name= "revtype" type= "integer" >
<constraints nullable= "false" />
</column>
<column name= "stuff" type= "varchar(36)" >
<constraints nullable= "true" />
</column>
</createTable>
<addPrimaryKey tableName= "stuff_aud" columnNames= "id, rev" />
</changeSet>
</databaseChangeLog>
The autoIncrement=”true” in the revinfo table is quite important. Without it, new revisions don’t have an ID and this exception occurs:
o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23502, SQLState: 23502
o.h.engine.jdbc.spi.SqlExceptionHelper : NULL nicht zulässig für Feld "REV"
NULL not allowed for column "REV"; SQL statement:
/* insert org.hibernate.envers.DefaultRevisionEntity */ insert into revinfo (rev, revtstmp) values (null, ?) [23502-200]
org.springframework.dao.DataIntegrityViolationException: could not execute statement ...
The best way to prevent this exception from happening is the mentionend autoIncrement. However, it’s also possible to create an entity for the revision:
import org.hibernate.envers.RevisionEntity ;
import org.hibernate.envers.RevisionNumber ;
import org.hibernate.envers.RevisionTimestamp ;
import javax.persistence.* ;
@Entity
@RevisionEntity
@Table ( name = "revinfo" )
public class Revision {
@Id
@GeneratedValue ( strategy = GenerationType . SEQUENCE )
@Column ( nullable = false , name = "REV" )
@RevisionNumber
private long rev ;
@Column ( name = "revtstmp" )
@RevisionTimestamp
private long timestamp ;
public long getRev () {
return rev ;
}
public void setRev ( long rev ) {
this . rev = rev ;
}
public long getTimestamp () {
return timestamp ;
}
public void setTimestamp ( long timestamp ) {
this . timestamp = timestamp ;
}
}
Here’s a great article about “The best way to implement an audit log using hibernate envers” by Vlad Mihalcea .