CF9 ORM - Clearing the session cache
Ben Nadel recently wrote an interesting post entitled Learning ColdFusion 9: When Does An ORM-Enabled Object Get Persisted. What Ben demonstrates is that when an Entity that has previously been saved is updated in a completely different request, the chnages that you make are automatically saved to the database when then request ends regardless of whether you called EntitySave or not. That last bit is important!
This all sounds well and good, but it does cause an issue if you design your applications so that validation occurs within the Entity. Essentially, this approach is where you can populate your object with all kinds of random data and then call a validation method which returns a result indicating if the entity is valid or not. Depending on the result, you either go ahead and save the data or return an bunch of validation error messages.
With the way that the hibernate sessions are handled in ColdFusion 9 (bearing in mind that it is still in Public Beta), you can't do this as when your request ends, ColdFusion will tell hibernate to commit any changes, including your invalid data. Not good.
Thankfully, there is a new function called ORMClearSession, which according to the docs: "ORMClearSession removes all the entities that are loaded or created in the session. This clears the first level cache and removes the objects that are not yet saved to the database."
What it doesn't make clear is that when it clears the cache, it doesn't commit any unsaved enties first. Here is some simple code to demonstrate.
User.cfc
/**
* I am a User Entity
*/
component persistent="true" table="tbl_User" output="false"
{
/*
Set up the persisted properties
The first line refers to metadata for ColdFusion
The second line refers to metadata required to create the database columns
*/
/**
* I am the unique identifier, which is numeric and generated for you
*/
property name="IDUser" fieldtype="id" generator="native" type="numeric" default="0"
ormtype="integer" notnull="true";
/**
* I am the Name of the User which maps to the user_Name column in the database
*/
property name="Name" type="string"
ormtype="string" column="user_name" length="50" notnull="true";
/**
* I am the Email Address of the User which maps to the user_Email column in the database
*/
property name="IsNew" type="boolean"
ormtype="string" column="user_new" length="20" notnull="true";
public any function init( name, isnew ) output="false"
{
if ( StructKeyExists( arguments, "name") )
{
this.setName( arguments.name );
}
if ( StructKeyExists( arguments, "isnew") )
{
this.setIsNew( arguments.isnew );
}
return this;
}
}
create.cfm
<cfset new User( "aliaspooryorik", true ) />
<cfset EntitySave( User ) />
<cfdump var="#User#" />
debug.cfm
<cfset User = EntityLoad( "User", {name="aliaspooryorik"}, true ) />
<cfdump var="#User#" />
update.cfm
<cfset User = EntityLoad( "User", {name="aliaspooryorik"}, true ) />
<cfset User.setNew( false ) />
<cfdump var="#User#" />
<cfif !StructKeyExists( url, "persist" )>
<!--- discard changes without saving them --->
<cfset ORMClearSession() />
</cfif>
- Posted in:
- ColdFusion
- Hibernate


Comment by Ben Nadel – August 03, 2009
Thanks for the original post - got the brain cells firing :)
Comment by John Whish – August 03, 2009
Super thanks.
Comment by John Allen – August 03, 2009
No problem - it's been many of your ORM posts that got my brain firing ;)
Comment by Ben Nadel – August 03, 2009
EntitySave( User );
// populate new object and validate here....
// validation fails so don't commit
ORMClearSession();
The changed entity will not be saved. If you need to the EntitySave to commit to the database then you'll need force an ORMFlush like this:
EntitySave( User );
ORMFlush();
// populate new object and validate here....
// validation fails so don't commit
ORMClearSession();
Comment by John Whish – August 03, 2009
Comment by Ben Nadel – August 03, 2009
There are definitely some other options -
Duplicate() the CFCs you want to populate, validate them, and then merge() them back into the current session.
You could also change the flushMode of the current session to only commit on Transaction commit, which would also solve a lot of problems.
Or if you really want to - just evict() the object that has been populated (although you may also have to evict it's children as well, unless you set them up with cascade=evict... I can't remember if you can do that in cfproperty).
I see too many people getting caught out using the clear all approach, as when they want SOME data to be stored, and some data to be ignored, they will get into trouble.
Comment by Mark Mandel – August 03, 2009
Ahh, that sounds good. I haven't gotten that far in the documentation yet.
Comment by Ben Nadel – August 04, 2009
As I understand it using Duplicate() would create a deep copy of the entity along with any objects that the entity references, which sounds like it might create as many problems as it solves.
My initial thought was to wrap the code in a transaction and I've got some code that does this, but that would be a real pain when porting an existing application.
Just from reading your comments, I like the idea of evicting the entity, but handling the children is going to be a pain as well.
Hmmm, great comments Mark - lots to think about :)
Comment by John Whish – August 04, 2009