Reputation: 190
I need to show history of modifications on entities on UI. Something like this -
|-------------------------------------------------------------------------------------------------------------------------------------| |Entity name | Entity ID | Action by | Action on | Action Type | Old value | New value | |-------------|-------------|-------------|----------------------|---------------|--------------------------|-------------------------| |Person | 1 | John Doe | 2017-04-12 12:00:00 | Add | | name: Lily, Role: Admin | |Person | 1 | John Doe | 2017-04-15 12:00:00 | Update |name: Lily | name: Lily Smith | |Subject | 5 | Mary | 2017-04-17 12:00:00 | Update |name: Maths | name: Mathematics | |Subject | 6 | Mary | 2017-04-17 12:00:00 | Delete |name: Science, credits: 5 | | |-------------------------------------------------------------------------------------------------------------------------------------|
I used Hibernate envers to store all the data I need. But I'm having problem reading up all the data to show something like I need above. I search a lot of blogs, Questions articles, documents, javadocs on the internet, but none of them shows how to do this. All of them show how to retrieve entities based on revision or return revisions. None of these entities have following information in addition to entities returned -
Please note that I want as least number of queries possible to speed up the history API response.
I'm using hibernate and envers version: '5.2.9.Final'
Any help in highly appreciated.
Thank you!
Here's the code -
Annotations above each entity class-
@Audited(withModifiedFlag = true)
Custom revision listener -
public class CustomRevisionListener implements RevisionListener { private static final Logger logger = LogManager.getLogger(CustomRevisionListener.class); public void newRevision(Object revisionEntity) { logger.info("newRevision, starts revisionEntity="+revisionEntity); CustomRevisionEntity revision = (CustomRevisionEntity) revisionEntity; String name = "unknown"; try{ //user custom userdetails.User to store id and name both? //or just store id in string format here? org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) SecurityContextHolder .getContext().getAuthentication().getPrincipal(); name = springUser.getUsername(); //get logged in username }catch(Exception e){ logger.error("error getting username", e); } logger.info("newRevision, name="+name); User user = new User(); user.setName(name); revision.setUser(user); //for testing logger.info("newRevision, setting userId="+user.getId()); } }
Custom revision entity -
@Entity @Table(name="REVISIONS") @RevisionEntity(CustomRevisionListener.class) public class CustomRevisionEntity extends DefaultRevisionEntity { private static final long serialVersionUID = -6113123831136684807L; @ManyToOne @JoinColumn(name="USER_ID") private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
envers config -
org.hibernate.envers.track_entities_changed_in_revision=true org.hibernate.envers.global_with_modified_flag=true org.hibernate.envers.store_data_at_delete=true org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy org.hibernate.envers.audit_strategy_validity_store_revend_timestamp=true
Reading audits -
public List getAllAudits() { logger.debug("getAllAudits starts"); //HERE IS WHERE I NEED HELP. //below code is just for trials. I need something which will read all audits and relevant info I described above ArrayList list = (ArrayList) AuditReaderFactory.get(entityManager) .createQuery() .forRevisionsOfEntity(Organization.class, true, true) // .add(AuditEntity.id().eq(bitacoraControlId)) .addOrder(AuditEntity.revisionNumber().asc()) .getResultList(); for(int i=0; i revisions = reader.getRevisions(Organization.class, 2); for(Number revNum:revisions){ Organization article =reader.find(Organization.class, 2, revNum); System.out.println("Revision No: " + revNum); System.out.println("Title: " + article.getId()); System.out.println("Content: " + article.getName()); } logger.debug("getAllAudits list size="+list.size()); return list; }
Upvotes: 4
Views: 4480
Reputation: 190
Ok, so I found my answer. Here is how I have to read audits for each type of Entity separately and merge them into one list -
AuditQuery query = AuditReaderFactory.get(entityManager)
.createQuery()
.forRevisionsOfEntity(clazz, false /*IMP*/, true)
.addOrder(AuditEntity.revisionNumber().desc());
//This return a list of array triplets of changes concerning the specified revision.
// The array triplet contains the entity, entity revision information and at last the revision type.
ArrayList<Object[]> list = (ArrayList) query.getResultList();
for(int i=0; i < list.size(); i++){
Object[] triplet = list.get(i);
BaseEntity entity = (BaseEntity) triplet[0];
CustomRevisionEntity revisionEntity = (CustomRevisionEntity) triplet[1];
RevisionType revisionType = (RevisionType) triplet[2];
...//populate DTO
}
If the RevisionType was "MOD" To get old value and new value with only the fields which were changed, I had to get the previous version and compare them field by field. Though Envers does record this field modified information in tables, but doesn't provide a good way to read.
public BaseEntity getPreviousVersion(BaseEntity entity, int current_rev) {
AuditReader reader = AuditReaderFactory.get(entityManager);
Number prior_revision = (Number) reader.createQuery()
.forRevisionsOfEntity(entity.getClass(), false, true)
.addProjection(AuditEntity.revisionNumber().max())
.add(AuditEntity.id().eq(entity.getId()))
.add(AuditEntity.revisionNumber().lt(current_rev))
.getSingleResult();
if (prior_revision != null)
return reader.find(entity.getClass(), entity.getId(), prior_revision);
else
return null;
}
Upvotes: 7