Aliaspooryorik
ColdFusion ORM Book

ORM hibernate sessions

I see quite a lot of confusion about how hibernate sessions work in ColdFusion 9's ORM (and I find it confusing as well!). So I thought I'd put together various examples to see what happens. These code samples are for ColdFusion 9.01 so if you're using 9.00 you will find that transactions work differently.

By default, ColdFusion will automatically save any modified objects at the end of a page request. Here's a quick example.


<cfscript>
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
EntitySave( Employee );
}
</cfscript>

Looking at the code above you'd may expect that the Employee will only be saved if the first name is not blank. In fact, running the code above will update the Employee record in the database even though we didn't explicity call EntitySave. This is because ColdFusion, by default will save any dirty (modified) objects automatically at the end of the request. This is not ideal so it is recommended that you use the this.ormsettings.flushatrequestend to false in you Application.cfc

After setting this.ormsettings.flushatrequestend to false in my Application.cfc, when I run the same code my Employee does not get updated in the database if the firstname is blank. However, we haven't solved the problem yet as if we run this code:


<cfscript>
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "Fred" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
EntitySave( Employee );
}
</cfscript>

The above code will not update the database _at all_, even though the firstname is not blank! Why? Well because EntitySave doesn't actually save an existing object (you do need it with new objects though), in fact you don't need to use EntitySave at all if you are modifying an existing record. Confusing huh! So how do you actually save the record?


<cfscript>
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "Fred" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
ORMFlush();
}
</cfscript>

OK, so I've replaced the EntitySave with an ORMFlush call. ORMFlush, makes hibernate commit any dirty (modified) objects to the database. So problem solved? Erm no! Let's look at something a bit more complicated.


<cfscript>
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
ORMFlush();
}

Department = EntityLoadByPK( "Department", 1 );
Department.setLocation( "Devon" );

// do some validation and save if OK
if ( Department.getLocation() != "" )
{
ORMFlush();
}
</cfscript>

In the example above, we want to the Department's location set to Devon and saved, but we don't want to set the Employee's name to a blank string, but as you may already have guessed, that is exactly what is going to happen. Using ORMFlush, will save to database _any_ modified objects.

OK, so we need to stop the Employee record from being persisted. Let's try clearing the hibernate session before we update the Department:


<cfscript>
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
ORMFlush();
}
else
{
ORMClearSession();
}

Department = EntityLoadByPK( "Department", 1 );
Department.setLocation( "Devon" );

// do some validation and save if OK
if ( Department.getLocation() != "" )
{
ORMFlush();
}
else
{
ORMClearSession();
}
</cfscript>

This time, when we run this script, then the Employee does not get persisted and the Department does. However, as we saw earlier ORMFlush is dangerous if we forget to use ORMSessionClear, and as we are dealing with persistence, we really should be using transactions (see Barney Boisvert's Why ORM Transactions are Crucial post. So, let's use some transactions. (It's worth noting that as Barney mentions in his post that if you're using ColdFusion 9 without the 9.01 update, then you need to use hibernate transactions like ORMGetSession().beginTransaction().)


<cfscript>
transaction
{
Employee = EntityLoadByPK( "Employee", 1 );
Employee.setFirstName( "" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
transaction action="commit";
}
else
{
transaction action="rollback";
}
} // end of transaction

transaction
{
Department = EntityLoadByPK( "Department", 1 );
Department.setLocation( "Devon" );

// do some validation and save if OK
if ( Department.getLocation() != "" )
{
transaction action="commit";
}
else
{
transaction action="rollback";
}
} // end of transaction
</cfscript>

In the above example, if the object is valid, then commit, otherwise rollback the changes. I should point out that in CF9.01 then ColdFusion no longer closes the hibernate session when you start or end a transaction, so you can still refer to the objects later in your code. (Bob Silverberg explains this in his post (ColdFusion 9.0.1 Now Available - With ORM Goodies)

So, this all works as I want it to, but let's try it on some composed objects.


<cfscript>
transaction
{
Employee = EntityLoadByPK( "Employee", 2 );
Employee.setFirstName( "" );

// do some validation and save if OK
if ( Employee.getFirstName() != "" )
{
transaction action="commit";
}
else
{
transaction action="rollback";
}
} // end of transaction

transaction
{
Department = EntityLoadByPK( "Department", 1 );
Department.setLocation( "Devon" );

// allocate the Department to the Employee (not bi-directional)
Employee.setDepartment( Department );

// do some validation and save if OK
if ( Department.getLocation() != "" )
{
transaction action="commit";
}
else
{
transaction action="rollback";
}
} // end of transaction
</cfscript>

When we run this code, then the Department changes are persisted, but the Department is not assigned to the Employee. I'm not 100% certain why that is, but I believe that it is because the Employee belongs to a different hibernate session (as ColdFusion creates a different session per transaction).

The major takeaways I've found from experimenting and reading blogs and the cf-orm-dev group is that all changes to ORM entities should be wrapped in a transaction and this.ormsettings.flushatrequestend should be set to false. There is more that I need try such as the implications of the autoManageSession setting and what impacts lazy loading has, but for now, this seems to work for me. Corrections as always are welcome :)


8 comments

  1. Thank you, my 1st CF9 ORM app is still using this.ormsettings.flushatrequestend = true (no such option pre CF9.01). What are some of the good reasons to flip it to false?

    Comment by Henry Ho – April 18, 2011
  2. Hi Henry, the flushatrequestend ormsettings was part of CF9.00 the autoManageSession was introducted in CF9.01.

    The main reason why you should be wary of using the default (of true) for flushatrequestend is that you could potentially save invalid data in your database, as changing any property of an entity you've loaded from the database will be persisted at the end of the request whether you want it to or not.

    Comment by John Whish – April 19, 2011
  3. @John,

    In your first example with transaction, I expected to see the same result as you mentioned. And then I turned off AutoManageSession! Try it. You will see the both will get persisted.

    Few key points to note:

    1. With AutoManageSession set to false, when the transaction ends, _all_ dirty entities will get persisted.

    2. With AutoManageSession set to true, when the transaction begins, _all_ existing session gets flushed.

    3. With AutoManageSession set to true, when you do transaction rollback session is cleared (ORMClearSession())

    Comment by Sumit Verma – April 19, 2011
  4. Hi Sumit - it was your thread on cf-orm-dev that prompted this post! There was something I read that just didn't seem right. Thanks for the bullet points :)

    As for point 1, that is how I would expect it to work, as that's how hibernate works. Point 3 is not something I've considered before. I've have to play around with that.

    Comment by John Whish – April 20, 2011
  5. I think #1 is not very intuitive. I know (now) hibernate works that way and developers there are certainly smarter than me ;-), but I think it's not giving us the 100% transactionality.

    Like in your first example you wouldn't expect employee to get saved.

    Transactions are suppose to be "unit of work", so, when I commit that unit it shouldn't affect other.

    Comment by Sumit Verma – April 20, 2011
  6. @Samit. I agree that point 1 is not intuitive, but I guess that's why AutoManageSession is true by default.

    Comment by John Whish – April 21, 2011
  7. The issue with that is you will run into detached objects which will cause error on lazy loading (#3). There are work around but nothing that's simple...

    Comment by Sumit Verma – April 22, 2011
  8. @Sumit, yes, that's true, however I've never had that issue as I also redirect after a failed validation.

    Comment by John Whish – April 26, 2011

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.

Please subscribe me to any further comments
 

Search

Wish List

Found something helpful & want to say ’thanks‘? Then visit my Amazon Wish List :)

Categories

Recent Posts