Sam Farmer head shot

Sam Farmer

Growing up I never imagined I would play bass guitar for the Dave Matthews Band. And indeed it never happened.

But I have become a passionate and pretty good web developer.


Searching ORM: First Why, Then How -- Slides from cf.Objective()

Thank you to everyone who came out to my presentation on Searching ORM at cf.Objective().

Here are the slides

Slides and Code: 5 Ways To Improve Your App with ORM

Here are the slides and code from my Adobe MAX ColdFusion Unconference talk 5 Ways To Improve Your App with ORM:

And the code which will set up the Derby DSNs and tables needed.

Available as a download and from Github. After installing:

  1. Go to setUp.cfm
  2. Enter in your cfadmin password and click 'Make Datasources'
  3. Go back to setUp.cfm and click on 'Go to App'

All of the mini apps/examples I used in the demo will now work.

Using structures/maps instead of arrays in ORM

By default an ORM relationship is an array. Using a structure (map for non-ColdFusion programmers) is very easy and provides nice options. Lets start with a simple relationship between of users and addresses.


User.cfc

view plain print about
1component persistent="true" {
2property name="userId" fieldtype="id" generated="always" generator="native";
3property name="name" ormtype="string";
4
5property name="address" fieldtype="one-to-many" cfc="address" fkcolumn="userId" inverse="true";
6}


Address.cfc

view plain print about
1component persistent="true" {
2property name="addressId" fieldtype="id" generated="always" generator="native";
3property name="type" ormtype="string";
4property name="street" ormtype="string";
5property name="city" ormtype="string";
6property name="state" ormtype="string";
7
8property name="user" fieldtype="many-to-one" cfc="user";
9}


A single user object with two addresses would look like this:


Now, if we want to find out if the user has a work address we need to loop through getAddress(). Thats ok but by converting the relationship to a struct its possible to then use structure functions. Here is the relationship in User.cfc defined to return a structure:

view plain print about
1property name="address" fieldtype="one-to-many" cfc="address" fkcolumn="userId" inverse="true"
2    type="struct" structkeycolumn="type";


A dump of the user object now looks like:


Which means we can use code like this, in particular line 3:

view plain print about
1<cfset u = entityLoad("user")>
2<cfloop array="#u#" index="user">
3    <cfif structKeyExists( user.getAddress(), "work" )>
4        <cfdump var="#user.getAddress()["work"]#">
5    </cfif>
6</cfloop>


For reference here is the Application.cfc to set up Hibernate/ORM:

view plain print about
1component
2{
3this.name="structsDemo";
4this.ormEnabled=true;
5this.datasource="cfartgallery";
6this.ormsettings = { dbcreate="update" };
7
8}

The one where I try ColdFusion 9 ORM secondary cache and decide its awesome

I've pretty much overlooked the secondary cache for ORM and, having now played with it, I've realized what a big feature and performance enhancer this is.

In short with secondary cache on ColdFusion can get data from ehCache instead of the database. Set up ColdFusion 9.0.1 and you can share entity data across both ColdFusion instances and servers!

Let backtrack and take a look at a basic set up:

Application.cfc
view plain print about
1component {
2this.datasource = "cacheFun";
3this.ormEnabled = true;
4this.ormSettings = {secondaryCacheEnabled=true, logsql=true};
5}

To turn on secondary cache set secondaryCacheEnabled to true (the default is false). In this example I have also turned logsql to true*. Now lets look at our persistent cfc:

User.cfc

view plain print about
1component persistent="true" cachename="userCache" cacheuse="transactional" {
2property name="id" column="id" ormtype="int" fieldtype="id" generated="always" generator="native";
3property name="firstname" ormtype="string";
4property name="lastname" ormtype="string";
5property name="version" fieldtype="version" datatype="int" ;
6
7function getLastName() {
8    return uCase (variables.lastname);
9}
10}

That really is it. The two cache attributes tell ColdFusion to use secondary cache for this data. From now on a call like:

view plain print about
1user = entityLoad("User", {id=1}, true);

will use the secondary cache when possible.

To help work out what secondary cache is doing I did two things in User.cfc:

  1. added a version field which would definitively show when Hibernate had updated the record (another very cool underused feature)
  2. added my own getter to verify that secondary cache returns data via the cfc and not just flat

Now there are four types of cacheuse, here are the differences:

  • transactional cache is updated on the update statement and the next request of the data will get it straight from cache and not run a select
  • nonstrict-read-write Cache is updated on the first select after data is updated or inserted
  • read-write I could not get this to work with caching (docs say its the worst performing anyway)
  • read-only Only works with data that will not be updated. Produces an error if you attempt to update or insert records. (Marc Esher wrote a long, funny blog entry about using secondary cache to cache for a read-only view)

I'm going to look more into secondary cache and hopefully come up with real examples but for now I know two things: 1) its very powerful, 2) its very easy to use.

* If ColdFusion is started from the Builder server panel the console tab will display all the SQL Hibernate produces. I used it to see when Hibernate went to cache and when the database.

How to use ORM and onSessionEnd()

At 16applications I use ORM extensively. Recently I wanted to track when users sessions timed out which the onSessionEnd function in Application.cfc is perfect for.

onSessionEnd is interesting in that its not part of a standard ColdFusion request process which ORM is somewhat dependent on. In order to use ORM inside it I had to use ormFlush() to get changes to flush to the database.

view plain print about
1function onSessionEnd( SessionScope ) {
2    if ( structKeyExists( arguments.SessionScope, "loginTrackerId" ) ) {
3        var lt = entityload("loginTracker", {loginTrackerId=arguments.SessionScope.loginTrackerId}, true );
4        lt.setOutDT( now() );
5        lt.setTimedOut( true );
6        ormFlush();
7    }
8}

ORM and Dates? You probably want timestamp

Data types for dates and times are slightly different across databases which often makes life difficult for us developers. ColdFusion ORM makes this pretty simple:

  • For a date with time use ormtype="timestamp"
  • Just a date use ormtype="date"

There are no other options!

Full example with the property tag:

view plain print about
1property name="aFullDateWithTime" ormtype="timestamp";
2property name="justADate" ormtype="date";

Slides for ORM presentation and a correction on Inverse

I really enjoyed giving my 15 things you should know about ORM presentation at CFUnited. Here are the slides:

Bob Silverburg came to me afterwards and corrected some of the information I had presented about the inverse property settings for relationships. Here is my latest attempt at explaining inverse:

  • it only impacts bi-directional relationships
  • both sides of the realtionship get add, has, remove functions
  • both sides of the relationship should be maintained with the above functions
  • Hibernate, though, will run two update statements. This is the need for setting inverse=true on the 'one-to-many' side. This will will tell Hibernate to only run one update statement.

Example of multiple datasources for ORM in 9.0.1

ColdFusion 9.0.1 brings multiple datasources support for ORM. Here is how to set it up.

In this example, we are going to use both the cfartgallery and cfbookclub datasources.  In the Application.cfc we will make the cfartgallery the default datasource.  Optionally we are going to set the dbcreate rules differently for the two datasources.  (We could also do this for the schema, catalog, dialect and sqlscript settings.)

 

component
{
this.name="multi";
this.ormEnabled=true;
this.datasource="cfartgallery";
this.ormsettings = { dbcreate={ cfartgallery="update",
cfbookclub="none"} };
}

 

From cfartgallery we will use the Art table as an object.  As cfartgallery is the default datasource, this is set up as usual:

 

component persistent="true"
{
property name="artID" fieldtype="id" ;
property name="artistID";
property name="artname";
property name="description";
property name="prize";
property name="largeimage";
property name="mediaID";
property name="issold";
property name="samColumn";
}

 

For any tables from the cfbookclub datasource there is an additional attribute "datasource" for the component:

 

component persistent="true" datasource="cfbookclub"
{
property name="bookID" fieldtype="id";
property name="authorID";
property name="title";
property name="bookdescription";
property name="isspotlight";
}

 

We can then get the data and dump it like so:

 

books = entityLoad("BOOKS", {}, {maxResults=2});
writeDump(books);
art = entityLoad("ART", {}, {maxResults=2});
writeDump(art);

 

 Pretty straighforward and pretty cool stuff.

Infinite entities, cfgrid and one cfc to handle the data

While writing SpreadEdit I wanted every entity to be editable via cfgrid.  In particular I thought it would be cool to have one cfc on the back end to process the data from cfgrid no matter what entity it was working with.  With ColdFusion 9 and ORM this proved possible and is pretty cool, check out the screencast.  Code the other side of the embed.

Here is the code for genericGrid.cfc:

<cfcomponent>
 
<cffunction name="getData" access="remote" returnformat="JSON">
     <cfargument name="page" required="true">
     <cfargument name="pageSize" required="true">
     <cfargument name="gridSortColumn" required="true">
     <cfargument name="gridSortDirection" required="true">
     <cfargument name="entity" required="true">
 
     <cfif ! len( arguments.gridSortColumn )>
          <cfset arguments.gridSortColumn = "1">
 
     </cfif>
 
     <cfset local.getTasks = ormExecuteQuery( " FROM #arguments.entity# ORDER BY #arguments.gridSortColumn# #arguments.gridSortDirection#" )>
 
    <cfreturn queryconvertforgrid( entityToQuery( local.getTasks ), Arguments.page, Arguments.pageSize)>
 
</cffunction>
 
<cffunction name="setData" access="remote">
     <cfargument name="action" required="true">
     <cfargument name="row" required="true">
     <cfargument name="changed" required="true">
     <cfargument name="entity" required="true">
     <cfif arguments.action eq "U">
          <cfset local.obj = entityLoad( arguments.entity, arguments.row, true )>
          <cfloop collection="#arguments.changed#" item="local.key">
               <cfinvoke component="#local.obj#" method="set#local.key#">
                    <cfinvokeargument name="#local.key#" value="#arguments.changed[ local.key ]#">
               </cfinvoke>
          </cfloop>
     <cfelseif arguments.action eq "I">
          <cfset local.obj = entityNew( arguments.entity )>
          <cfloop collection="#arguments.row#" item="local.key">
               <cfif local.key neq "CFGRIDROWINDEX">
                    <cfinvoke component="#local.obj#" method="set#local.key#">
                         <cfinvokeargument name="#local.key#" value="#cleaned( arguments.row[ local.key ] )#">
                    </cfinvoke>
               </cfif>
          </cfloop>
     </cfif>
     <cfset entitySave( local.obj )>
</cffunction>
 
<cffunction name="cleaned" access="private" >
     <cfargument name="string" required="true">
     <cfset var ret = arguments.string>
     <!--- check for date --->
     <cfif reFind( "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}T[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}",
               ret )>
          <cfset ret = rereplaceNoCase( ret, "T", " " )>
     </cfif>
     <cfreturn ret>
</cffunction>
 
 
</cfcomponent>

Thoughts on my first ColdFusion 9 application going into production

I recently released my first application built with ColdFusion 9 to production and it took full advantage of some of the new features of ColdFusion 9.  Overall these features sped up development and have nudged me to become a better developer.

ORM

Damn this is cool.  Create a new cfc with persistent=true, write all the needed properties, and as long as the ormsettings include dbupdate=update and ormReload() is called all the database table creation is complete.  Then use entityNew, entityLoad and entitySave to work with the object(s).  Making changes to the model is as simple as adding a property.  Setting up event handlers to automatically deal with audit data improves the applications quality and is also damn cool!  That added with the code assist from CFBuilder makes development very rapid.

But beyond the ease and speed I also feel that ORM has made me a better object-oriented developer as I noticed  I finally really stopped thinking about tables and instead objects.

Improved CFScript

After all these years of loving writing everything CF in tags it feels a little weird to switch to loving script but its less code, cleaner and easier to read.  Now I love using cfscript for writing CF in the model and controller layers.  For views good old tags are still my preferences.   The only disapointment is that not all tags are available in script (and way fewer than as suggested by Ben Forta).  I hope that will be enhanced in either 9.0.1 or 10.

Caching

This application retrieved a lot of information from third parties so to increase speed and reliability I did a fair amount of caching.  It doesn't get much easier than using cachePut and cacheGet which work with every data type (xml, structures, strings, etc).

Virtual File System

While I have not done any fully scientific, load balanced tests my quick tests show code running faster from the VFS which makes sense as its in RAM.  ColdFusion treats the VFS just like any other file system which makes using the VFS extremely easy.  The only problems I encountered came about if the VFS connection was not closed properly and that file could then often not be used until a restart.  To accomodate for this I added try/catch around use of VFS where an error might occur and closed the files in the catch.

There are many, many more features of ColdFusion 9 and I tried to cram as many as possible into my Best of ColdFusion 9 submission SpreadEdit as possible but this application only used the above ones!

Overall I've really enjoyed building applications with ColdFusion 9.  Its definitely faster in both performance and development, especially when combined with ColdFusion Builder.  I think it may well shift how we build applications.

More Entries

BlogCFC was created by Raymond Camden. This blog is running version 5.9.7. Contact Blog Owner