Search code examples
javaspringhibernatespring-mvchibernate-envers

Hibernate Envers doesn't write anything inside the Audit Tables with SpringMVC config


I'm trying to use Enverse to audit the tables when I save, update or delete an entry in my db.

The Envers configure is the follow:

pom.xml

        <!-- Spring -->
        <org.springframework-version>4.1.6.RELEASE</org.springframework-version>

        <!-- Hibernate  -->
        <hibernate.version>4.3.5.Final</hibernate.version>
[...]

    <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

My jpa-tx-config.xml

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="packagesToScan" value="my.domain"/>

    <property name="persistenceUnitName" value="persistenceUnit"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop> 
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
            <prop key="hibernate.connection.charSet">UTF-8</prop>

            <prop key="hibernate.max_fetch_depth">3</prop>
            <prop key="hibernate.jdbc.fetch_size">50</prop>
            <prop key="hibernate.jdbc.batch_size">20</prop>
            <prop key="hibernate.show_sql">false</prop>
            <prop key="hibernate.format_sql">true</prop>

            <prop key="org.hibernate.envers.audit_table_suffix">_H</prop>
            <prop key="org.hibernate.envers.revision_field_name">AUDIT_REVISION</prop>
            <prop key="org.hibernate.envers.revision_type_field_name">ACTION_TYPE</prop>
            <prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">AUDIT_REVISION_END</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">True</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">AUDIT_REVISION_END_TS</prop>               
            <prop key="jadira.usertype.databaseZone">jvm</prop>
        </props>
    </property>
</bean>

Every my domains class have the @Audited annotation, but in DB this fields are ever null.

I don't understand what's wrong, any suggestion?

EDIT

In response of a comment: Every entity class extends an abstract domain that implements the Auditable way. Follow the code:

   @SuppressWarnings("serial")
@MappedSuperclass
@Audited
public abstract class AbstractDomain implements Auditable<String, Long>, Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @Version
    private int version;

    @JsonIgnore
    @Column(updatable=false)
    private String createdBy;

    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    @DateTimeFormat(iso=ISO.DATE_TIME)
    @JsonIgnore
    @Column(updatable=false)
    private DateTime createdDate;

    @JsonIgnore
    private String lastModifiedBy;

    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    @DateTimeFormat(iso=ISO.DATE_TIME)
    @JsonIgnore
    private DateTime lastModifiedDate;

The only field that is modified is the version, the other fields are ignored.


Solution

  • From what I'm seeing you actually don't need envers at all. Envers is more about storing revisions of your data (which can be used for auditing purposes of course) but if you want just to have these createdBy,createdDate,lastModifiedBy,lastModifiedDate fields there is a much simpler way.

    Since you're already using spring-data-jpa I would suggest you to enable JPA auditing with @EnableJpaAuditing annotation.

    Then you can drop @Audited from your AbstractDomain class and add @EntityListeners(AuditingEntityListener.class) which will force hibernate to save auditing information every time when entity got persisted.

    And the last, but not least thing to do is to define AuditorAware bean. It will tell who exactly is manipulating given entity at each moment so auditing listener will know what data should be set to createdBy and lastModifiedBy fields. It is quite simple, here is an example:

    @Bean
    AuditorAware auditor() {
        // return () -> "system";  // Fixed principal
        // and for spring-security
        return () -> {
            Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();
            if (authentication == null || !authentication.isAuthenticated()) {
                return null;
            }
            return authentication.getPrincipal().toString();
        }
    }
    

    And thats it.

    Here is a full example:

    @SpringBootApplication
    @EnableJpaAuditing
    public class So45347635Application {
        public static void main(String[] args) { SpringApplication.run(So45347635Application.class, args); }
    
        @MappedSuperclass
        @EntityListeners(AuditingEntityListener.class)
        public static abstract class AbstractDomain extends AbstractPersistable<Long> implements Auditable<String, Long> {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
            @Version
            private int version;
    
            @Column(updatable = false)
            private String createdBy;
    
            @Temporal(TemporalType.TIMESTAMP)
            @Column(updatable = false)
            private Date createdDate;
    
            private String lastModifiedBy;
    
            @Temporal(TemporalType.TIMESTAMP)
            @Column(insertable = false)
            private Date lastModifiedDate;
    
            @Override
            public String getCreatedBy() {
                return this.createdBy;
            }
    
            @Override
            public void setCreatedBy(String createdBy) {
                this.createdBy = createdBy;
            }
    
            @Override
            public DateTime getCreatedDate() {
                return null == this.createdDate ? null : new DateTime(this.createdDate);
            }
    
            @Override
            public void setCreatedDate(DateTime createdDate) {
                this.createdDate = createdDate.toDate();
            }
    
            @Override
            public String getLastModifiedBy() {
                return this.lastModifiedBy;
            }
    
            @Override
            public void setLastModifiedBy(String lastModifiedBy) {
                this.lastModifiedBy = lastModifiedBy;
            }
    
            @Override
            public DateTime getLastModifiedDate() {
                return null == this.lastModifiedDate ? null : new DateTime(this.lastModifiedDate);
            }
    
            @Override
            public void setLastModifiedDate(DateTime lastModifiedDate) {
                this.lastModifiedDate = lastModifiedDate.toDate();
            }
        }
    
        @Entity
        @Table(name = "users")
        public static class User extends AbstractDomain {
            private String username = "anonymous";
    
            public String getUsername() {
                return username;
            }
    
            public void setUsername(String username) {
                this.username = username;
            }
    
            @Override
            public String toString() {
                return String.format("User{id=%d, createdBy='%s', createdDate=%s, lastModifiedBy='%s', lastModifiedDate=%s, username='%s'}",
                        getId(), getCreatedBy(), getCreatedDate(), getLastModifiedBy(), getLastModifiedDate(), username);
            }
        }
    
        @Bean
        AuditorAware auditor() { return () -> "system"; }
    
        @Bean
        CommandLineRunner start(UserRepository userRepository, TransactionTemplate tx) {
            return args -> tx.execute(ctx -> {
                final User user = userRepository.save(new User());
                user.setUsername("admin");
                System.out.println(">>>> " + userRepository.save(user));
                return null;
            });
        }
    }
    
    interface UserRepository extends CrudRepository<So45347635Application.User, Long> {
    }
    

    Output:

    create table users (id bigint generated by default as identity, created_by varchar(255), created_date timestamp, last_modified_by varchar(255), last_modified_date timestamp, version integer not null, username varchar(255), primary key (id));
    insert into users (id, created_by, created_date, last_modified_by, version, username) values (null, 'system', '28-Jul-17', 'system', 0, 'anonymous');
    update users set last_modified_by='system', last_modified_date='28-Jul-17', version=1, username='admin' where id=1 and version=0;