Thanks for the ideas Mike. After a little mulling, I think your idea of dynamically modeling relationships to the audit table at application startup is the best one I’ve heard so far. I’m going to take a peek down that road tomorrow :)
Cheers, - hugi > On 10. ágú. 2015, at 14:24, Mike Kienenberger <mkien...@gmail.com> wrote: > > Not sure if it's clear, but there are two different approaches to your > problem described. > > 1) You can set a foreign relationship to your audit table and let > Cayenne assign the key at commit. This is probably the easiest > especially if you dynamically create the relationships in java code. > Except for the initialization of these relationships at some point > (startup or first time you hit the audit code), nothing else has to be > done and that's the only "cayenne-internals" code you have to deal > with. > > 2) You can use something like the example code to grab the primary key > values after cayenne generates them but before the actual database > query executes. I doubt that prePersist is early enough, but it might > be. Otherwise you will have to go looking for a different hook. > This requires a lot more knowledge of cayenne internals in my opinion. > This is also a much harder problem if you're deferring the key > generation to the database rather than using sequences or some similar > approach, but Cayenne obviously has a way to handle that now or > regular foreign key setup for relationships wouldn't work. > > Your third idea of using postPersist has its own problems. If you > decide to use a postPersist and set this information in a separate > commit, then you run the risk of your audit log not being created and > losing your audit information. For my environment, that wasn't > acceptable. Maybe you could wrap both commits in a single > transaction -- I'm not certain, but maybe that could work. > Transaction support either wasn't there back when I was doing this or > I didn't understand that it could solve my problem back then. > > > On Mon, Aug 10, 2015 at 10:13 AM, Hugi Thordarson <h...@karlmenn.is> wrote: >> Thanks Mike! Although the approach I’m working on is a little different (and >> meant to be reusable with any Cayenne installation so can’t depend on >> superclass template modifications), it’s very helpful to see code from other >> Cayenne folks. >> >> Cheers, >> - hugi >> >> // Hugi Thordarson >> // http://www.loftfar.is/ <http://www.loftfar.is/> >> // s. 895-6688 >> >> >> >>> On 10. ágú. 2015, at 12:53, Mike Kienenberger <mkien...@gmail.com> wrote: >>> >>> I set up auditing using a different approach in one project many years >>> ago back in Cayenne 1.1, and I've continued using it up to this point >>> in 3.x. I generated special setter, addTo, and removeFrom methods as >>> well as a create method which created the logger object at that point. >>> >>> To get the primary key, I set up one-way object relationships between >>> the primary key of the logged object and the foreign key storage field >>> in the audit log, one for each object entity. When the commit >>> happened, the relationships automatically populated the audit log >>> fields. You probably can do the same thing. There's probably a >>> better way to set up the object relationships than manually defining >>> them in your model these days. >>> >>> In a different Cayenne 1.2 project, I used something similar to your >>> pre-persist hook, although we didn't have prePersist yet. For this >>> one, I had two foreign record key fields, one used for a single column >>> primary key and one used for compound primary keys. I'm not sure if >>> the code will still work outside of 1.2 since this project was never >>> upgraded, but here it is in case you want to try this approach and >>> adapt it to a more recent version of Cayenne. >>> setForeignKeyRepresentation(Map pkAttributes, DbEntity dbEntity, Map >>> auditRecordMap) and the code that calls it after setting pkAttributes >>> would likely be what you want to reference for fetching the primary >>> key dynamically. >>> >>> >>> import java.io.Serializable; >>> import java.util.ArrayList; >>> import java.util.Collections; >>> import java.util.Date; >>> import java.util.HashMap; >>> import java.util.Iterator; >>> import java.util.List; >>> import java.util.Map; >>> >>> import org.objectstyle.cayenne.CayenneRuntimeException; >>> import org.objectstyle.cayenne.ObjectId; >>> import org.objectstyle.cayenne.access.DataContext; >>> import org.objectstyle.cayenne.access.DataDomainFlushObserver; >>> import org.objectstyle.cayenne.access.DataNode; >>> import org.objectstyle.cayenne.access.DefaultDataContextDelegate; >>> import org.objectstyle.cayenne.dba.PkGenerator; >>> import org.objectstyle.cayenne.map.DataMap; >>> import org.objectstyle.cayenne.map.DbAttribute; >>> import org.objectstyle.cayenne.map.DbEntity; >>> import org.objectstyle.cayenne.map.ObjEntity; >>> import org.objectstyle.cayenne.query.BatchQuery; >>> import org.objectstyle.cayenne.query.DeleteBatchQuery; >>> import org.objectstyle.cayenne.query.InsertBatchQuery; >>> import org.objectstyle.cayenne.query.UpdateBatchQuery; >>> >>> public class AuditLoggingDataContextDelegate extends >>> DefaultDataContextDelegate implements Serializable >>> { >>> public static final String MOD_TYPE_INSERT = "I"; >>> public static final String MOD_TYPE_UPDATE = "U"; >>> public static final String MOD_TYPE_DELETE = "D"; >>> >>> public void finishedRunQueries(DataContext dataContext, List queryList) { >>> super.finishedRunQueries(dataContext, queryList); >>> >>> Date modificationDate = new Date(); >>> >>> List auditRecordMapList = new ArrayList(); >>> >>> Iterator queryIterator = queryList.iterator(); >>> while (queryIterator.hasNext()) { >>> BatchQuery batchQuery = (BatchQuery) queryIterator.next(); >>> >>> if (batchQuery instanceof InsertBatchQuery) >>> { >>> InsertBatchQuery insertBatchQuery = >>> (InsertBatchQuery)batchQuery; >>> insertBatchQuery.reset(); >>> >>> List dbAttributes = insertBatchQuery.getDbAttributes(); >>> while(insertBatchQuery.next()) { >>> for(int i = 0; i < dbAttributes.size(); i++) { >>> Map auditRecordMap = new HashMap(); >>> >>> DbAttribute dbAttribute = (DbAttribute) >>> dbAttributes.get(i); >>> Object value = insertBatchQuery.getValue(i); >>> >>> DbEntity dbEntity = (DbEntity)dbAttribute.getEntity(); >>> auditRecordMap.put("MOD_TIME", modificationDate); >>> auditRecordMap.put("SCHEMA_NAME", >>> dbEntity.getSchema()); >>> auditRecordMap.put("TBL_NAME", dbEntity.getName()); >>> auditRecordMap.put("COL_NAME", dbAttribute.getName()); >>> auditRecordMap.put("MOD_TYPE", MOD_TYPE_INSERT); >>> if (null != value) >>> { >>> auditRecordMap.put("NEW_VALUE", value.toString()); >>> } >>> >>> Map pkAttributes; >>> ObjectId objectId = insertBatchQuery.getObjectId(); >>> if (null != objectId) >>> { >>> pkAttributes = objectId.getIdSnapshot(); >>> } >>> else >>> { >>> pkAttributes = >>> insertBatchQuery.getCurrentObjectSnapshot(); >>> } >>> setForeignKeyRepresentation(pkAttributes, >>> dbEntity, auditRecordMap); >>> >>> auditRecordMapList.add(auditRecordMap); >>> } >>> } >>> } >>> else if (batchQuery instanceof DeleteBatchQuery) >>> { >>> DeleteBatchQuery deleteBatchQuery = >>> (DeleteBatchQuery)batchQuery; >>> deleteBatchQuery.reset(); >>> >>> List dbAttributes = deleteBatchQuery.getDbAttributes(); >>> while(deleteBatchQuery.next()) { >>> for(int i = 0; i < dbAttributes.size(); i++) { >>> Map auditRecordMap = new HashMap(); >>> >>> DbAttribute dbAttribute = (DbAttribute) >>> dbAttributes.get(i); >>> >>> DbEntity dbEntity = (DbEntity)dbAttribute.getEntity(); >>> auditRecordMap.put("MOD_TIME", modificationDate); >>> auditRecordMap.put("SCHEMA_NAME", >>> dbEntity.getSchema()); >>> auditRecordMap.put("TBL_NAME", dbEntity.getName()); >>> auditRecordMap.put("COL_NAME", dbAttribute.getName()); >>> auditRecordMap.put("MOD_TYPE", MOD_TYPE_DELETE); >>> >>> >>> setForeignKeyRepresentation(deleteBatchQuery.getCurrentQualifier(), >>> dbEntity, auditRecordMap); >>> >>> auditRecordMapList.add(auditRecordMap); >>> } >>> } >>> } >>> else if (batchQuery instanceof UpdateBatchQuery) >>> { >>> UpdateBatchQuery updateBatchQuery = >>> (UpdateBatchQuery)batchQuery; >>> updateBatchQuery.reset(); >>> >>> List dbAttributeList = >>> updateBatchQuery.getUpdatedAttributes(); >>> while(updateBatchQuery.next()) { >>> for(int i = 0; i < dbAttributeList.size(); i++) { >>> Map auditRecordMap = new HashMap(); >>> >>> DbAttribute dbAttribute = (DbAttribute) >>> dbAttributeList.get(i); >>> Object newValue = updateBatchQuery.getValue(i); >>> >>> Object oldValue = updateBatchQuery.getOldValue(i); >>> >>> DbEntity dbEntity = (DbEntity)dbAttribute.getEntity(); >>> auditRecordMap.put("MOD_TIME", modificationDate); >>> auditRecordMap.put("SCHEMA_NAME", >>> dbEntity.getSchema()); >>> auditRecordMap.put("TBL_NAME", dbEntity.getName()); >>> auditRecordMap.put("COL_NAME", dbAttribute.getName()); >>> auditRecordMap.put("MOD_TYPE", MOD_TYPE_UPDATE); >>> if (null != oldValue) >>> { >>> auditRecordMap.put("OLD_VALUE", >>> oldValue.toString()); >>> } >>> if (null != newValue) >>> { >>> auditRecordMap.put("NEW_VALUE", >>> newValue.toString()); >>> } >>> >>> >>> setForeignKeyRepresentation(batchQuery.getObjectId().getIdSnapshot(), >>> dbEntity, auditRecordMap); >>> >>> auditRecordMapList.add(auditRecordMap); >>> } >>> } >>> } >>> } >>> >>> processAuditRecordMapList(dataContext, auditRecordMapList); >>> } >>> >>> protected void processAuditRecordMapList(DataContext dataContext, >>> List auditRecordMapList) >>> { >>> SecIndividual secIndividual = >>> (SecIndividual)dataContext.getUserProperty("secIndividual"); >>> SecSystem secSystem = >>> (SecSystem)dataContext.getUserProperty("secSystem"); >>> >>> // Sort into ChangeLog records >>> Map changeLogMap = new HashMap(); >>> Iterator auditRecordMapIterator = auditRecordMapList.iterator(); >>> while (auditRecordMapIterator.hasNext()) { >>> Map auditRecordMap = (Map) auditRecordMapIterator.next(); >>> >>> auditRecordMap.put("SYSTEM_ID", secSystem.getPrimaryKey()); >>> auditRecordMap.put("REAL_USER_ID", secIndividual.getPrimaryKey()); >>> auditRecordMap.put("EFFECTIVE_USER_ID", >>> secIndividual.getPrimaryKey()); >>> >>> String tableName = (String)auditRecordMap.get("TBL_NAME"); >>> DbEntity dbEntity = >>> dataContext.getEntityResolver().getDbEntity(tableName); >>> DataMap dataMap = dbEntity.getDataMap(); >>> String changeLogObjEntityName = "ChangeLog" + dataMap.getName(); >>> ObjEntity changeLogObjEntity = >>> dataMap.getObjEntity(changeLogObjEntityName); >>> DbEntity changeLogDbEntity = changeLogObjEntity.getDbEntity(); >>> >>> List changeLogList = (List)changeLogMap.get(changeLogDbEntity); >>> if (null == changeLogList) >>> { >>> changeLogList = new ArrayList(); >>> changeLogMap.put(changeLogDbEntity, changeLogList); >>> } >>> >>> changeLogList.add(auditRecordMap); >>> } >>> >>> Iterator changeLogMapEntryIterator = >>> changeLogMap.entrySet().iterator(); >>> while (changeLogMapEntryIterator.hasNext()) { >>> Map.Entry changeLogEntry = (Map.Entry) >>> changeLogMapEntryIterator.next(); >>> DbEntity changeLogDbEntity = (DbEntity)changeLogEntry.getKey(); >>> List changeLogList = (List)changeLogEntry.getValue(); >>> >>> InsertBatchQuery batch = new >>> InsertBatchQuery(changeLogDbEntity, changeLogList.size()); >>> >>> DataNode node = >>> dataContext.getParentDataDomain().lookupDataNode(changeLogDbEntity.getDataMap()); >>> PkGenerator pkGenerator = node.getAdapter().getPkGenerator(); >>> List dbAttributeList = changeLogDbEntity.getPrimaryKey(); >>> if (1 != dbAttributeList.size()) >>> { >>> throw new CayenneRuntimeException("Compound primary key"); >>> } >>> DbAttribute keyAttribute = (DbAttribute)dbAttributeList.get(0); >>> String key = keyAttribute.getName(); >>> Iterator changeLogIterator = changeLogList.iterator(); >>> while (changeLogIterator.hasNext()) { >>> Map auditRecordMap = (Map) changeLogIterator.next(); >>> ObjectId id = >>> createObjectIdForAuditLog(changeLogDbEntity, node, pkGenerator, key); >>> auditRecordMap.put(key, id.getIdSnapshot().get(key)); >>> batch.add(auditRecordMap, id); >>> } >>> >>> DataDomainFlushObserver observer = new DataDomainFlushObserver(); >>> node.performQueries(Collections.singletonList(batch), observer); >>> } >>> >>> } >>> >>> private ObjectId createObjectIdForAuditLog(DbEntity >>> changeLogDbEntity, DataNode node, PkGenerator pkGenerator, String key) >>> throws CayenneRuntimeException { >>> Object pkValue; >>> try { >>> pkValue = pkGenerator.generatePkForDbEntity(node, >>> changeLogDbEntity); >>> } catch (Exception e) { >>> throw new CayenneRuntimeException("Error generating audit >>> log primary keys", e); >>> } >>> ObjectId id = new ObjectId(changeLogDbEntity.getName(), key, pkValue); >>> return id; >>> } >>> >>> private void setForeignKeyRepresentation(Map pkAttributes, >>> DbEntity dbEntity, Map auditRecordMap) { >>> Integer primaryKeyOfRecord = null; >>> String primaryKeysString = null; >>> >>> // References to the record that was changed (FK_C is for >>> compound keys, FK is for a single integer key). >>> >>> if (1 == pkAttributes.size()) >>> { >>> Iterator pkIterator = pkAttributes.keySet().iterator(); >>> String primaryKeyName = (String) pkIterator.next(); >>> Object pkObject = pkAttributes.get(primaryKeyName); >>> if (pkObject instanceof Integer) >>> { >>> primaryKeyOfRecord = (Integer)pkObject; >>> } >>> } >>> >>> if (null == primaryKeyOfRecord) >>> { >>> Iterator pkIterator = pkAttributes.keySet().iterator(); >>> while (pkIterator.hasNext()) >>> { >>> String primaryKeyName = (String) pkIterator.next(); >>> Object primaryKeyValue = pkAttributes.get(primaryKeyName); >>> >>> if (null == primaryKeysString) >>> { >>> primaryKeysString = primaryKeyName + "=" + >>> primaryKeyValue; >>> } >>> else >>> { >>> primaryKeysString = primaryKeysString + "," + >>> primaryKeyName + "=" + primaryKeyValue; >>> } >>> } >>> } >>> >>> if (null != primaryKeyOfRecord) >>> { >>> auditRecordMap.put("FOREIGN_KEY", primaryKeyOfRecord); >>> } >>> if (null != primaryKeysString) >>> { >>> auditRecordMap.put("FKEY_CONDITION", primaryKeysString); >>> } >>> } >>> } >>> >>> >>> >>> On Mon, Aug 10, 2015 at 7:27 AM, Aristedes Maniatis <a...@maniatis.org> >>> wrote: >>>> On 10/08/2015 8:31pm, Hugi Thordarson wrote: >>>>> Is it possible for me to obtain the primary key for a Cayenne DataObject >>>>> before committing changes? I’m writing an audit log and I need the key >>>>> for the object during PrePersist (where I’m constructing the log object). >>>> >>>> How will it have a primary key before the record is written to the >>>> database? Or do you want to hang onto the temporary ObjectId and then >>>> replace it with the real PK after the commit? >>>> >>>> Ari >>>> >>>> >>>> -- >>>> --------------------------> >>>> Aristedes Maniatis >>>> GPG fingerprint CBFB 84B4 738D 4E87 5E5C 5EFA EF6A 7D2E 3E49 102A >>