In the previous entry, I described the basic blog application I want to create. We looked at the model, the relationships needed, and whipped up the CFCs to implement that model.

One of the things I mentioned is that I want each entity to be able to keep track of when it was created and who created it. This could be done manually, but I like to avoid manual whenever I can. The good news is that we can easily do this automatically.

The solution is to use Hibernate's event model. In Hibernate terms, the classes that are notified about persistence activities are called interceptors. Happily, the ColdFusion development team have given us access to this capability by letting us specify event handler CFCs in our ORM configuration.

I should note that I am running CF 9.0.1 with the cumulative hotfix installed. I'll set up my Application.cfc like this:

component name="Application.cfc" output="false"
{
    this.name = Hash( GetCurrentTemplatePath() );
    
    this.sessionManagement = true;
    this.sessionTimeout = CreateTimeSpan( 0, 0, 0, 10 );
    this.setClientCookies = true;
    this.setDomainCookies = true;
    
    this.appRoot = ExpandPath( '.' );
    this.mappings = {};
    this.mappings["/ormblog"] = this.appRoot;
    
    this.datasource = "ormblog";
    this.ormEnabled = true;
    this.ormSettings.dialect = "MySQLwithInnoDB";
    this.ormSettings.logSQL = true;
    this.ormSettings.saveMapping = false;
    this.ormSettings.eventHandler = 'ormblog.model.orminterceptor.AuditInterceptor';
    this.ormSettings.flushAtRequestEnd = false;
    this.ormSettings.autoManageSession = false;
    this.ormsettings.dbcreate = "dropcreate";
}
		

You can see that I'm specifying an AuditInterceptor CFC as an ORM event handler. Any event handler CFC we define must implement the CFIDE.ORM.IEventHandler interface, so my AuditInterceptor looks like this:

import ormblog.model.domain.*;

component output="false" implements="CFIDE.ORM.IEventHandler"
{
    public void function preInsert( any entity )
    {
   	 entity.setDateCreated( Now() );
   	 if( IsNull( entity.getCreatedBy() ) 
             && StructKeyExists( session, 'currentUser' ) )
   	 {
   		 entity.setCreatedBy( EntityLoadByPK( 'User', session.currentUser ) );
   	 }
    }
    
    public void function preLoad( any entity )
    {
    }
    
    public void function postLoad( any entity )
    {
    }
    
    public void function postInsert( any entity )
    {
    }
    
    public void function preUpdate( any entity, Struct oldData )
    {
    }
    
    public void function postUpdate( any entity )
    {
    }
    
    public void function preDelete( any entity )
    {
    }
    
    public void function postDelete( any entity )
    {
    }

}
		

In my preInsert() method, I'm setting the dateCreated, and setting the createdBy to the currently logged in User. In a real application, I would probably avoid having my interceptor reach into the session scope by using ColdSpring to inject a Session Proxy object that encapsulates interaction with the session scope. But in the interest of keeping this example simple and focused, we'll just grab the current user's ID from the session scope.

The end result of this is that any time I persist a new entity, the dateCreated and createdBy properties will be set automatically. And this could easily be expanded, for example if I ever want to track dateUpdated or updatedBy, etc.

If I only wanted to perform this audit tracking on certain CFCs, rather than all subclasses of my Entity class, I could create another subclass of Entity called AuditableEntity and have my CFCs extend that, or I could create an Auditable interface and have my CFCs implement that interface. The AuditInterceptor could then be changed to only set the audit properties if the CFC being saved is of type AuditableEntity/Auditable. But I digress. ;-)

We've now completed the automatic population of dateCreated and updatedBy. In the next entry I'll dig deeper into the bi-directional relationship between BlogEntry and BlogComment.

Comments Comments (5) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 6398 Views

Comments

  • # Posted By spills | 9/10/10 10:51 AM

    First, thanks for taking the time to put this series together and totally look forward to more! I noticed this line in your Application.cfc; this.ormSettings.autoManageSession = false and am wondering what is the difference with this.ormSettings.flushAtRequestEnd = false? What else is CF doing with the Hibernate Session that makes you want to have disable autoManageSession?

    Thanks

  • # Posted By Brian | 9/10/10 11:12 AM

    Sure, automanage session allows me to control how the Hibernate session is handled. This was added in 9.0.1 and was a badly needed improvement IMHO. From the docs (at http://help.adobe.com/en_US/ColdFusion/9.0/Develop...):

    Lets you specify if ColdFusion must manage Hibernate session automatically.

    If enabled: ColdFusion manages the session completely. That is, it decides when to flush the session, when to clear the session, and when to close the session.

    If disabled: The application is responsible for managing flushing, clearing, or closing of the session. The only exception is (in the case of transaction), when the transaction commits, the application flushes the session.

    ColdFusion closes the ORM session at the end of request irrespective of this flag being enabled or disabled.

  • # Posted By Tim Garver | 12/28/11 12:34 PM

    Hey Brian,
    Thanks for the post. I found if very useful.

    I am in the middle of a project and wondered if it is possible to use the PostLoad() to rename a column?

    For instance, i have a two tables, one has custom1,custom2,...
    and the other has user defined names for those custom fields.

    <code>
    public void function postLoad(){      
             if(!isNull(this.getcustom1())){
    //some how rename custom1 to myfield
    }

          }
    </code>

  • # Posted By Brian Kotek | 12/28/11 1:02 PM

    I'm not sure what you mean by "rename". If there is a public property named what you wish, you could set it with something like "setYourPropertyName( getCustom1() )". But that doesn't sound like what you're asking.

  • # Posted By Tim Garver | 12/30/11 11:39 AM

    I have a persisted cfc named customers
    and it has 2 public properties named custom1 and custom2.

    and i have another persisted cfc named custom_fields
    it has public properties of
    custom1_name
    custom2_name

    Ideally when i return the customs object, if there are values in the custom1 or 2 fields, i would like to somehow replace the name custom1 with the value from custom_fields.getCustom1_name()

    But the more I think about it the more sketchy it sounds lol.. I will have to come up with another way to display the data and names.

    Thanks again
    Tim