|
|
|
Actually many of these things are doable via a TransactionDelegate now... It is just a matter of organizing things into something usable, but the hook is there and it allows to commit/rollback in the same DB transaction.
OK. That is very helpful. I can see how it might work. But from http://www.objectstyle.org/confluence/display/CAYDOC/Understanding+Transactions it seems that we would normally have one TransactionDelegate per domain. So if we commit several entities in one context we would need to iterate through each entity in the context and perform the appropriate trigger code. So if we commit five invoices, each one would need to update Contact.owing.
Ideally we would have willCommit, didCommit, etc available within the entity class. In the meantime we could probably achieve something similar with reflection from the TransactionDelegate but it isn't quite as nice. We don't want to use "User-Defined Transaction Scope" because this code should be invisible to the client (in a three tier architecture). Here's the cayenne changes needed for Cayenne 1.2 to implement a DataContextDelegate.finishedRunQueries(DataContext, List queries) method and to augment the BatchQueries to track necessary auditing information.
ExampleOfCreatingAuditLogRecordsInSameCommit.zip contains one way you can translate BatchQueries into audit records in the same commit.
Here's what the fields of an audit record look like in this example. <?xml version="1.0" ?> <table> <column java.sql.Types="4" keyOrder="1" name="ID" nullable="false" position="5" primaryKey="true" type="INTEGER" /> <column java.sql.Types="12" keyOrder="0" name="SCHEMA_NAME" nullable="true" position="11" primaryKey="false" size="32" type="VARCHAR" /> <column java.sql.Types="12" keyOrder="0" name="TBL_NAME" nullable="true" position="13" primaryKey="false" size="32" type="VARCHAR" /> <column java.sql.Types="12" keyOrder="0" name="COL_NAME" nullable="true" position="1" primaryKey="false" size="32" type="VARCHAR" /> <column java.sql.Types="12" keyOrder="0" name="MOD_TYPE" nullable="true" position="7" primaryKey="false" size="1" type="VARCHAR" /> <column java.sql.Types="12" keyOrder="0" name="OLD_VALUE" nullable="true" position="9" primaryKey="false" size="2000" type="VARCHAR" /> <column java.sql.Types="12" keyOrder="0" name="NEW_VALUE" nullable="true" position="8" primaryKey="false" size="2000" type="VARCHAR" /> <column java.sql.Types="4" keyOrder="0" name="FOREIGN_KEY" nullable="true" position="4" primaryKey="false" type="INTEGER" /> <column java.sql.Types="12" keyOrder="0" name="FKEY_CONDITION" nullable="true" position="3" primaryKey="false" size="200" type="VARCHAR" /> <column java.sql.Types="93" keyOrder="0" name="MOD_TIME" nullable="true" position="6" primaryKey="false" size="6" type="TIMESTAMP" /> <column java.sql.Types="4" keyOrder="0" name="SYSTEM_ID" nullable="true" position="12" primaryKey="false" type="INTEGER" /> <column java.sql.Types="4" keyOrder="0" name="EFFECTIVE_USER_ID" nullable="true" position="2" primaryKey="false" type="INTEGER" /> <column java.sql.Types="4" keyOrder="0" name="REAL_USER_ID" nullable="true" position="10" primaryKey="false" type="INTEGER" /> </table> ID is meaningless primary key SCHEMA_NAME.TBL_NAME.COL_NAME describes path to data changed. MOD_TYPE is insert, delete, or update (I,D,U) OLD_VALUE and NEW_VALUE are the state changes. FOREIGN_KEY and FKEY_CONDITION are references to the record that was changed (FK_C is for compound keys, FK is for a single integer key). MOD_TIME is when the record was changed. SYSTEM_ID/EFFECTIVE_USER_ID/REAL_USER_ID describes where the data was changed. (application and user). Note that the specifics of what to do with the generated audit records is encapsulated in processAuditRecordMapList() -- override this method to do your own processing of the audit records.
By the way, there's one bug to correct in my example code:
AuditLoggingDataContextDelegate.java:line 142 should be deleted. (It duplicates the section above it, but without the null check.) if (null != newValue) { auditRecordMap.put("NEW_VALUE", newValue.toString()); } auditRecordMap.put("NEW_VALUE", newValue); Note that there's currently a 2.0 version of this code mixed in with the 2.0 patch for
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This hook needs to run after the commit is inevitable (that is, after all validation has been done), but be able to create/modify records which are part of the same database transaction as the rest of the context changes.
Would it be possible to have two new hooks: onSave() and onDelete(), or a single onCommit() hook? These would run once for every persistent object being saved. In three tier, this code should run on the server.