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.
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;