cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Hugi Thordarson <h...@karlmenn.is>
Subject Re: Obtain primary key for DataObject before commitChanges
Date Tue, 11 Aug 2015 22:13:43 GMT
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 <mkienenb@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 <hugi@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 <mkienenb@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 <ari@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
>> 


Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message