Mastodon

Creating Tables for Hibernate Envers with Liquibase

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.