📜 ⬆️ ⬇️

Hibernate Envers: listening operations

Why is this necessary?


In enterprise development, it is often necessary to track the editing process of any tables.
Let's say you got a document in the system. Then someone changed his series. Then the number. Then I changed the type from the passport of the Russian Federation to a foreign country. passport of Germany. I would like to be able to track the history of changes and, say, to click on the header of the user who made a mistake.



What are we using?

We will proceed from the fact that our system works with the database through Hibernate. In this case, we have an excellent mechanism for listening to records: Envers.
')
What we get in the end?

For each table whose changes we want to track, Envers will create us with an extra table in which we will put a record every time we do something with an audited table. For example:

Table Document (DOCUMENT):
IDSERIESNUMBER
one1000100,000


Audit table for documents: (DOCUMENT_AUD)

IDRevREVTYPESERIESNUMBER
oneone01000100,000
one2one1345100,000


As can be seen from the table, we have 2 revisions for the document with id = 1. One (with REVTYPE = 0) is the newly added record, the second is the change of the record. The 1000 series has become 1345.

Okay, tell me, but when did it happen? Envers generates another table, called REVINFO by default. It contains the revision number and timestamp. But the number of fields can be expanded.

What does it take for all this to take off?

From libraries, we will not need anything but hibernate itself.

You just need to add the following lines to persistence.xml, but I think everyone understands why they are needed:

<property name="hibernate.ejb.event.post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-delete" value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" /> 


To indicate that an entity needs to be audited, you must specify the Auditable annotation, like this:

 @Entity @Audited public Document 


If you generate a schema by class, then in order to add audit tables, you need to change the classname of the antiva task that runs hbm2ddl:

 classname="org.hibernate.tool.ant.EnversHibernateToolTask" 


So, we have a label with versions and a table with the time for these revisions. But what if we want to slightly expand this table and give it a different name? We are in a hurry to help annotation @RevisionEntity.

Define your entity:

 @Entity @org.hibernate.envers.RevisionEntity(RevisionListener.class) public class RevisionEntity extends DefaultRevisionEntity { } 


DefaultRevisionEntity here is a default entity that already contains timestamp and revision number. If you want to override something, you can do this:

 @Entity @RevisionEntity(ExampleListener.class) public class ExampleRevEntity { @Id @GeneratedValue @RevisionNumber private int id; @RevisionTimestamp private long timestamp; ... } 


@RevisionNumber and @RevisionTimestamp can be used only once, so if you inherit from DefaultRevisionEntity, you can’t use them a second time.

How to supplement our essence? Just like any other!

 @Entity @org.hibernate.envers.RevisionEntity(RevisionListener.class) public class RevisionEntity extends DefaultRevisionEntity { @ManyToOne private User user; // ,  } 


But the question is: when should we put this additional data? For this you need a RevisionListener. This is a class that implements org.hibernate.envers.RevisionListener and has one method:

public void newRevision(Object revision);

It will be called when our audited entity (Document) changes. The revision object here is our RevisionEntity. So we can write something like

 RevisionEntity revisionEntity = (RevisionEntity) revision; revisionEntity.setUser(...); //  -   ,  


But how to read the data?


Since you do not have an entity that maps to the audit table (Document_AUD), you cannot write a normal query. But Envers comes to your rescue. To read this table we will need AuditQuery. You can get it like this:

 AuditQuery query = AuditReaderFactory.get(em).createQuery(); 


Well, then we just have to say what exactly we need, a specific revision or maybe a list of revisions:

 query.forRevisionsOfEntity(Document.class, false, false); 


Flags are very important here. If the last one is true, the query will return the deleted records as well.
But the second flag regulates the return value. If true, then only the revisions themselves will be listed. And if not, then a list of arrays of 3 elements is returned.

The first is revision, the second is RevisionEntity, and the third is the type of revision.

You can attach various filtering to this query, for example, by the id of the main entity:

 query.add(AuditEntity.id().eq(docId)) 


Or by RevisionEntity parameters:

 query.add(AuditEntity.revisionProperty("user_id").eq(userId)) 


but now (hibernate 3.6.1 - 3.6.4) this feature does not work, for a bug .

And finally, we get the list:

 List<Object []> resultList = query.getResultList(); 


Audit strategies


For query efficiency, it is recommended to use ValidityAuditStrategy. This strategy adds another field to the _AUD table - REVEND. Thus, it is not necessary to do nested selects in order to get the maximum revision.

To enable it, you just need to add a line to persistence.xml:

  <property name="org.hibernate.envers.audit_strategy" value="org.hibernate.envers.strategy.ValidityAuditStrategy"/> 


Literature:

Hibernate envers

This is an article by habrauser dzigoro , asked to publish.

Source: https://habr.com/ru/post/120631/


All Articles