Hibernate Envers doesn't write anything in audit tables with SpringMVC config
I am trying to use Enverse to check tables when saving, updating or deleting a record in my db.
Envers is configured as follows:
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>
Each class of my domains has an annotation @Audited
, but these fields are always in the database null
.
I don't understand what is wrong, any suggestion?
EDIT
In response to comment: Each entity class extends an abstract domain that implements the Auditable method. 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 changed is the version, the rest of the fields are ignored.
source to share
From what I can see, you don't need it at all envers
. Envers is more about keeping revisions
your data (which can be used for auditing purposes, of course), but if you just want to have these fields createdBy
, createdDate
, lastModifiedBy
, lastModifiedDate
, easier way.
Since you are already using spring-data-jpa
, I suggest you include JPA auditing
with annotation @EnableJpaAuditing
.
Then you can drop @Audited
from your class AbstractDomain
and add @EntityListeners(AuditingEntityListener.class)
which will make hibernate save the audit information every time the entity is saved.
Last but not least, define the AuditorAware
bean. He will tell you exactly who is manipulating this object at every moment, so the auditor must know what data should be set in the createdBy
and fields lastModifiedBy
. It's pretty simple, here's 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 here it is.
Here's a complete 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;
source to share