Reputation: 63
I am trying to audit the action that the user performed that resulted in changes to corresponding tables. For example if a user were to transfer money between 2 accounts this would generate the following sequence of events:
The parent audit message for all tables would be: "User generated transfer for amount XXX"
This is achieved with the following schema: schema
alt text http://img48.imageshack.us/img48/7460/auditloggingiv6.png
The question is how do I represent this in hibernate?
I have created the following:
In Balance and Transfer's mapping files
<set name="auditRecords" table="TransferAuditRecord" inverse="false" cascade="save-update">
<key>
<column name="AuditRecordID" not-null="true" />
</key>
<one-to-many class="audit.AuditRecord"/>
</set>
Transfer and Balance classes then implement IAuditable which has methods
public void setAuditRecords(Set<AuditRecord> auditRecord);
public Set<AuditRecord> getAuditRecords();
In AuditRecord's mapping file I have:
<many-to-one name="parentAuditRecord" lazy="false"
column="parent_id"
class="audit.AuditRecord"
cascade="all" />
Then in Logging class using AOP and Hibernate Interceptors I have:
AuditRecord auditRecord = new AuditRecord();
auditRecord.setUser(userDAO.findById(
org.springframework.security.context.SecurityContextHolder.getContext()
.getAuthentication().getName()));
auditRecord.setParentAuditRecord(getCurrentActiveServiceRecord());
auditable.getAuditRecords().add(auditRecord);
Then in the Service Class I call the following method, enclosed in a transaction:
save(balance1);
save(balance2);
transfer.setPassed(true);
update(transfer);
The parentAuditRecord is created using AOP with a thread safe stack, and the AuditRecordType_id is set using annotations on the method.
I left out the "passed" column on the transfer table. Previously I call save(transfer) to insert the transfer amount into the Transfer table with passed set to false. (This action is also audited).
My requirements are slightly more complicated than the example above :P
So the sequence of events for the above should be:
However the cascade options defined above fail at the update statement. Hibernate refuses to insert a record into the many-to-many table (even if unsaved-value="any" on the AuditRecord Mapping). I always want to insert rows into the many-to-many tables so potentially one Transfer has many Audit Records marking the previous events. However, the latest event determines the message the user wants to see. Hibernate either tries to update the many-to-many table and previous AuditRecord entries or it simply refuses to insert into AuditRecord and TransferAuditRecord, throwing a TransientObjectException.
The Audit Message is retrieved something like this:
msg=... + ((AuditRecord) balance.getAuditRecords().toArray()[getAuditRecords().size()-1])
.getParentAuditRecord().getAuditRecordType().getDescription() + ...;
The message should say something like this: "Username set transfer to passed at 12:00 11-Oct-2008"
EDIT I decided to go with explicitly mapping the many-to-many table (with an associated interface), and then in afterTransactionCompletion, calling save on the parent audit record (which cascades the save to the child audit records) then explicitly saving the interface on all child mapping tables. This isn't a true audit history, rather a non-invasive method of recording user action. I will look into Envers if I need more complete audit history at a later point.
Upvotes: 3
Views: 2064
Reputation: 30920
At the design level, it seems like a insert only db design would work marvels here.
If you want to keep it the way it is right now (which I'm sure you do), you could look into Hibernate listeners/interceptors/events (well defined in the doc: http://www.hibernate.org/hib_docs/v3/reference/en-US/html_single/)
Else, I just looked into JBoss Envers and it also seems pretty useful.
Upvotes: 0
Reputation: 9303
Seems like the relationship between parentAuditRecord and transferauditrecord and balance auditrecord shouldn't be one to many. When I read what you typed I'm seeing it as a table per subclass usage of that audit hierarchy which is a one-to-one relationship.
http://www.hibernate.org/hib_docs/reference/en/html/inheritance.html
You may also want to check out JBoss's Envers project.
Upvotes: 1