ENVERS
WHAT IS IT?
Hibernate module for Entity Auditing
ENTITY
@Entity
public class Device {
@Id
private long id;
@Type(type = "text")
private String configuration;
public static Device of(long id, String configuration) { ... }
public long getId(){ ... }
public String getConfiguration(){ ... }
public void setId(long id){ ... }
public void setId(String configuration){ ... }
}
SAVE IT
final long id = 1;
transactionTemplate.execute(status -> {
final Device device = Device.of(id, "configuration 1");
hibernate.getCurrentSession().merge(device);
});
// save it again, it changed configuration
transactionTemplate.execute(status -> {
final Device device = Device.of(id, "configuration 2");
hibernate.getCurrentSession().merge(device);
});
HEY, TELL ME...
What was the previous configuration?
What was the configuration yesterday?
All the times whan a configuration change occurred?
"Dunno, LOL!"
"Dunno, LOL!"
"Dunno, LOL!"
AUDITED ENTITY
@Entity
@Audited // <--------
public class Device {
@Id
private long id;
@Type(type = "text")
private String configuration;
public static Device of(long id, String configuration) { ... }
public long getId(){ ... }
public String getConfiguration(){ ... }
public void setId(long id){ ... }
public void setId(String configuration){ ... }
}
SAVE IT
final long id = 1;
transactionTemplate.execute(status -> {
final Device device = Device.of(id, "configuration 1");
hibernate.getCurrentSession().merge(device);
});
// save it again, it changed configuration
transactionTemplate.execute(status -> {
final Device device = Device.of(id, "configuration 2");
hibernate.getCurrentSession().merge(device);
});
HEY, TELL ME...
What was the previous configuration?
What was the configuration yesterday?
All the times whan a configuration change occurred?
long revision = auditReader.getCurrentRevision(Rev.class, true).getId() -2;
String configuration = auditReader.find(Device.class, id, revision).getConfiguration();
long revision = auditReader.getRevisionNumberForDate(yesterday);
String configuration = auditReader.find(Device.class, id, revision).getConfiguration();
auditReader.createQuery().forRevisionsOfEntity(Device.class, false, false)
.add(AuditEntity.id().eq(id))
.getResultList()
.stream()
.map(entityRevisionAndType -> ((Rev)entityRevisionAndType[1]).getTimestamp())
.collect(Collectors.toList());
SOME CONCEPTS
- 1 revision per transaction
- only transactions that modify audited entities create a revision
- revision number is global, not for entity (CVS-like)
- revisions are kept in an "audit table" for the entity (entity + change info)
WHEN USE IT?
- need to query back in time
- already using hibernate
- simple queries* (at revision or for revisions)
* Can use for more complex things too, but either query time (hibernate) or complexity (sqlQueries) skyrockets.
SETUP MAVEN
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>4.3.7.Final</version><!-- current hibernate version -->
</dependency>
SETUP SESSIONFACTORY
@Bean
public LocalSessionFactoryBean localSessionFactoryBean(...) {
final Properties p = new Properties();
// default strategy doesn't mark validity end time of a revision
p.put("org.hibernate.envers.audit_strategy",
"org.hibernate.envers.strategy.ValidityAuditStrategy");
// some weird stuff happens if you delete and then recreate an entity
// with the same id without this set to true
p.put("org.hibernate.envers.allow_identifier_reuse", true);
// suffixes overrides ...
p.put("org.hibernate.envers.audit_table_suffix", "_auditing");
p.put("org.hibernate.envers.revision_field_name", "_revision");
p.put("org.hibernate.envers.revision_type_field_name", "_revision_type");
p.put("org.hibernate.envers.audit_strategy_validity_end_rev_field_name", "_revision_end");
// we needed this, but it's usually useless to audit it.
p.put("org.hibernate.envers.do_not_audit_optimistic_locking_field", false);
final LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
// snip...
factoryBean.setHibernateProperties(p);
return factoryBean;
}
More here
SETUP CUSTOM REV
@Entity
@RevisionEntity
public class Rev {
@Id
@GeneratedValue
@RevisionNumber
private long id;
@RevisionTimestamp
private long timestamp;
// snip getters, setters, hashcode, equals, toString
}
More here
CAVEATS
- MUST use Criteria API, Envers doesn't notice entity changes when using HQL update/delete queries
- Some defaults create weird behaviour for fairly reasonable cases (validity strategy, id reuse)
- Default revision entity holds only number and timestamp, will be often replaced with custom one
- Some AuditCriterions that join entities across revisions may generate an unexpected quantity of db hits with sequential subqueries
- data is duplicated in the audit table on change
KNOWN UNKNOWNS
- migrations
- entity relationships
(What we know we didn't try)
THANKS
envers
By Tsukihara Caligin
envers
- 1,595