ColdFusion 9 ORM example
Mark Mandel has written a great article for the Adobe Devnet on Introducing ORM in Adobe ColdFusion 9 Beta. I just thought I'd hop on the bandwagon and blog about a simple example demonstrating some of the ORM features using the in-built cfartgallery datasource so you can run it without setting up a database.
I've created two CFC's called Art.cfc and Artist.cfc, these are my persistant CFCs. In others they are want I want to be saved in my database. I've written these using the nice new cfscript enhancements with JavaDoc style comments/metadata.
Let's look at Art.cfc first.
/**
* I am a Art Persisted Entity
* @output false
* @table ART
* @persistent true
*/
component
{
// identifier
property name="ArtID" fieldtype="id" generator="native" ;
// properties
property name="ArtName" ;
property name="Description" ;
property name="Price" ;
property name="LargeImage" ;
property name="IsSold" ;
property name="Artist" fieldtype="many-to-one" fkcolumn="ArtistID" cfc="Artist";
}
OK, this is pretty simple. I have an identifier (think primary key), some properties and a relationship to my Artist Entity. The relationship simply tells ColdFusion that a piece of Art has one Artist. I won't go into two much detail about this as it is all in Mark's article, I'll just say that from this ColdFusion will auto-magically create the getter and setter (accessors/mutators) methods for each property:
- getArtName()
- setArtName()
- getDescription()
- setDescription()
- getPrice()
- setPrice()
- getLargeImage()
- setLargeImage()
- getIsSold()
- setIsSold()
- getArtID()
- setArtID()
Pretty cool huh!
If you're wondering how ColdFusion knows about datatypes, then it simply introspects the database and figures it all out for you (assuming the database exists!). ColdFusion can actually create the tables for you, but I'll leave that for another post.
Moving on to my Artist.cfc.
/**
* I am a Artist Persisted Entity
* @output false
* @table ARTISTS
* @persistent true
*/
component
{
// identifier
property name="ArtistID" fieldtype="id" generator="native";
// properties
property name="Firstname";
property name="Lastname";
property name="Address";
property name="City";
property name="State";
property name="PostalCode";
property name="Email";
property name="Phone";
property name="Fax";
property name="ThePassword";
/* one artist can have many... */
/* return an array of Art objects */
property name="Art" fieldtype="one-to-many" cfc="Art" fkcolumn="ArtistID" type="array" orderby="Price Asc";
/* if we wanted to return a struct of Art objects we'd use this syntax */
// property name="Art" fieldtype="one-to-many" cfc="Art" fkcolumn="ArtistID" type="struct" structkeycolumn="ArtID" orderby="Price Asc";
/**
* I return the number of itemd of art associated with this Artist
* @output false
*/
public numeric function getArtCount()
{
if ( this.hasArt() )
{
return ArrayLen( this.getArt() );
}
else
{
return 0;
}
}
/**
* I return the number of items sold for this artist
* @output false
*/
public numeric function getSoldItemsCount()
{
return ORMExecuteQuery(
"SELECT COUNT(*) FROM Art as ArtEntity WHERE ArtEntity.Artist.ArtistID=:ArtistID AND ArtEntity.IsSold=:IsSold",
{ ArtistID=this.getArtistID(), IsSold=True }
, True );
}
/**
* I return the number of itemd of art associated with this Artist
* @output false
*/
public string function getFullname()
{
return this.getFirstName() & " " & this.getLastname();
}
}
Right, a bit more going on in this one. The sharp eyed among you may have noticed that the CFC is called Artist.cfc but the database table is called ARTISTS. ColdFusion handles this mapping for you if you specify the table attribute in your cfcomponent tag, or use the JavaDoc style metadata like I have above. If you don't include this attribute, then ColdFusion will assume that the Entity name and the Database table name are the same.
I've got a relationship to my Art cfc defined. This time one Artist can have many Art objects. In this example I'm returning an array of Art objects, but ColdFusion also allows you to return a struct of Art objects which can be pretty useful. I've commented the struct syntax out above but thought I'd show it. When you define a one-to-many or a many-to-many relationship then ColdFusion will generate the following methods for your:
- addArt( ArtEntity )
- hasArt()
- getArt()
- removeArt()
More info on generated methods is available here:
http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSf0ed2a6d7fb07688310730d81223d0356fc-7fff.html
I've added a few of my own methods to the Artist.cfc which I'll use later. These are getArtCount(), getSoldItemsCount() and getFullname(). The getFullname() is the simplest, all it does is concatinate the first and last name values. Note that I've prefixed "this." to my method name. You'll need to do that, otherwise ColdFusion will throw an error when referencing generated methods.
The getArtCount() method, simply checks to see if the Artist has any Art by called this.hasArt(), if it does then I return the size of the array of Art objects.
getSoldItemsCount() introduces a new function in ColdFusion, ORMExecuteQuery. All this does is to query your objects (not I said objects and not database). The syntax is very similar to SQL (it's called HQL), but you reference your objects and their properties instead of database tables and column names. In the code above, I'm getting a count of all the Art for the Artist, which has been sold.
So how do we use our new fangled Persistant Entites? Well, here's some sample code to demonstrate:
Application.cfc
component
{
this.name = Hash( GetCurrentTemplatePath() );
/* define the application wide datasource */
this.datasource = "cfartgallery";
/* enable hibernate support for this application */
this.ormenabled = true;
/* create a struct of ORM settings */
this.ormsettings = {};
/* turn on event handling */
this.ormsettings.eventhandling = true;
/**
* @output true
*/
public boolean function onRequestStart( targetPage )
{
/* this is just to ensure that ORM is up-to-date for demo */
ORMReload();
return true;
}
}
index.cfm
<!---- get all artists ordered by first name, note the second argument is a blank filter ---->
<cfset artists = EntityLoad( "Artist", {}, "firstname asc" ) />
<cfoutput>
<table width="400">
<tr>
<th>Artist</th>
<th>Items</th>
<th>Sold Items</th>
</tr>
<cfloop array="#artists#" index="index">
<tr>
<cfif index.hasArt()>
<td><a href="art.cfm?artistid=#index.getArtistID()#">#index.getFullName()#</a></td>
<cfelse>
<td>#index.getFullName()#</td>
</cfif>
<td>#index.getArtCount()#</td>
<td>#index.getSoldItemsCount()#</td>
</tr>
</cfloop>
</table>
</cfoutput>
art.cfm
<!---- get artist by id ---->
<cfset artist = EntityLoad( "artist", url.artistid, True ) />
<cfoutput>
<h2>#artist.getFullName()#</h2>
<table width="400">
<tr>
<th>Item</th>
<th>Price</th>
<th>Sold</th>
</tr>
<cfloop array="#artist.getArt()#" index="index">
<tr>
<td>#index.getArtName()#</td>
<td>#index.getPrice()#</td>
<td>#index.getIsSold()#</td>
</tr>
</cfloop>
</table>
<p><a href="index.cfm">View list</a></p>
</cfoutput>
I've bundled it all up into a zip for anyone who's interested.
- Posted in:
- ColdFusion
- OOP


property userId;
property firstName;
property lastName;
Comment by Dan Vega – July 13, 2009
I quite like the syntax I used in the post as I think it looks a bit odd when you define identities with named attributes (but that's just personal preference!). I think you can also define the named attributes using the JavaDoc style but I haven't tried it (yet!).
Comment by John Whish – July 13, 2009
property name="SoldArt" fieldtype="one-to-many" cfc="Art" fkcolumn="ArtistID" type="array" where="IsSold=1" orderby="Price Asc";
This is what I concluded from reading the docs on relationships like this. Excellent post and thanks for taking the time to write it in cfscript.
Comment by Drew – August 03, 2009
Comment by John Whish – August 04, 2009
Mapping for component Artist not found.
Either the mapping for this component is missing or the application must be restarted to generate the mapping.
Any help would be appreciated.
thanks
dan
Comment by dan fredericks – November 09, 2009
Comment by John Whish – November 09, 2009
Comment by John Whish – November 09, 2009
What was the resolution of the "mapping for component not found" error? I'm stuck in this situation myself, and I can't make heads nor tails of it.
Thanks!
Comment by Nathan Strutz – December 01, 2009
Comment by David – December 19, 2009
Comment by John Whish – December 19, 2009
Comment by kenlam – December 25, 2009
Comment by John Whish – December 27, 2009
coldfusion.orm.EntityMappingNotFoundException: Mapping for component Books not found.
at coldfusion.orm.hibernate.HibernatePersistenceManager.getEntityName(HibernatePersistenceManager.java:196)
Which leads me to believe that out of the box it's not finding the Entity object in Hibernate.
Suggestions?
Comment by John Hall – December 28, 2009
Comment by John Hall – December 28, 2009
Can anyone point me in the correct direction as to what i am doing wrong ?
Thanks
Faheem
Comment by faheem – April 18, 2010
Let me ask this then, since I'm on a shared host, does the server have to have something set up? Maybe the hosting service has ORM turned off or something?
Comment by Don – May 26, 2010
Comment by John Whish – May 28, 2010
Comment by Don – May 28, 2010
Comment by John Whish – May 28, 2010
Though I found out, that the problem lies in my application residing in a path with a folder name containing a dot (like /abc/firstname.lastname/xyz).
Removing the dot from the folder name solved the problem for me.
Though I need the dot inside the name. So my questions are: Is this a bug in CF? Is there a way around this issue (when keeping the dot)?
Thanks in advance!
Comment by Sebastian Zartner – August 24, 2010
What you should be able to do is create mappings for any folders that have a dot in the name, and then use the alias you created for the mapping instead of the folder name.
Hope that helps.
Comment by John Whish – August 24, 2010
<cfset person = createObject("person")>
<cfset persons = entityLoad("person")>
Creating the object worked well ("component" as first parameter is not needed anymore in CF9). Obviously entityLoad() is working different to createObject() regarding its entity name parameter.
Comment by Sebastian Zartner – August 24, 2010
Comment by John Whish – August 24, 2010
Nice thought tho.
Comment by Don – August 24, 2010
Having said that if your code works for you then one of the benefits of using ColdFusion is that it doesn't force you to write code in any particular way!
Comment by John Whish – August 25, 2010
[cfset artists = EntityLoad( "Artist", {}, "firstname asc" )]
Comment by Reinhard Jung – October 11, 2010
I didn't find the answer here, but on further experimentation, there wasn't anything wrong with the mapping, there was an error in the component itself. (not a helpful error message!)
The offending line was this:
<cfproperty name="measurementDate" ormtype="date" default="#Now()#">
I commented out the entityLoad() function, and just loaded the component with a cfobject tag, and only then did I get a useful error message: properties have to be a constant value. So I got rid of the "default" attribute, restarted ColdFusion, and was able to use the entityLoad() function again.
So I would recommend that to anyone who hasn't solved their mapping issue - replace the entityLoad() with a cfobject and see if there's an error in the component itself.
P.S. is there any way I can set the default value to the current date?
Comment by Daniel – November 17, 2010
component persistent="true" {
property name="measurementDate" ormtype="date";
function any init(){
if (IsNull(variables.measurementDate)){
variables.measurementDate = Now();
}
return this;
}
}
Comment by John Whish – November 22, 2010
Comment by Daniel – November 23, 2010
I'm thinking you should probably use the getters and setters to set the 'measurementDate ':
function init(){
if (isNull(getmeasurementDate ())){
setmeasurementDate (now());
}
return this;
}
Thoughts?
Comment by James Brown – December 02, 2010
groups.google.com/group/cfcdev/browse_thread/thread/b5adf5a37071f989
Comment by John Whish – December 02, 2010
Comment by cru – October 17, 2011
The way to fix this though is to remember that the ORM system caches the bean paths on startup. When you introduce a new bean it probably won't see it until the application is restarted.
The easiest way to sort this is to execute ORMReload(). I now have this in my FW/1 'setupUpApplication()' method which runs when the framework starts up or is force reloaded.
Probably a good way of handling this to make sure the ORM system can see new objects that you introduce.
Comment by James Allen – October 21, 2011