ColdFusion 9 ORM example

July 13, 2009

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:

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:

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.

 


25 comments

  1. Nice Writeup! Just a quick example with properties. If all you are doing is providing the property name in script you can use this is as shorthand.

    property userId;
    property firstName;
    property lastName;

    Comment by Dan Vega – July 13, 2009
  2. Hey Dan - thanks :) Good tip about the property shorthand.

    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
  3. I kept getting errors when calling your getSoldItemCount function... something about Art is not mapped. I instead added a new relationship with a where that filtered on sold items.

    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
  4. Hi Drew, I guess that's the problem with writing code samples for a beta build! You probably want to check out the filter attribute of cfproperty which lets you do some cool stuff :)

    Comment by John Whish – August 04, 2009
  5. Here is the error I get when I add all these files into an ORM folder:

    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
  6. Hey dan, I've just tested the code in the post and I'm not getting any errors so I'm not sure why you are getting that message. I'll ping you my code.

    Comment by John Whish – November 09, 2009
  7. @Dan, I tried to reply to send you a zip to your email address but it's returning "DNS Error: Domain name not found" so let me know of another email address I can use.

    Comment by John Whish – November 09, 2009
  8. Hey John, Dan,
    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
  9. I have the same issue here about Either the mapping for this component is missing or the application must be restarted to generate the mapping. I have got one table works perfectly but when I introduce another one,, I have the error message. Now, I am stuck

    Comment by David – December 19, 2009
  10. @David I've finally got round to attaching the zip file (at the bottom of the post). Can you download that and let me know if that works for you?

    Comment by John Whish – December 19, 2009
  11. Just as the situation above, I also found the info about "Mapping for component XXX not found" and have no way to solve it. Beacuse of the two tables in my database are isolated, so I did not created the relationship between them, was it the cause of the problem?

    Comment by kenlam – December 25, 2009
  12. If someone wants to send me their code I'll be happy to have a look!

    Comment by John Whish – December 27, 2009
  13. I'm getting the same thing and rds debugging I see the message:

    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
  14. Whoops, bouncing back and forth between examples. The entity is looking for Artist component

    Comment by John Hall – December 28, 2009
  15. hi there i am starting to use coldbox and using the same example Task manager and tried to run it but get the same error: coldfusion.orm.EntityMappingNotFoundException: Mapping for component Task not found.

    Can anyone point me in the correct direction as to what i am doing wrong ?

    Thanks
    Faheem

    Comment by faheem – April 18, 2010
  16. Well, I too am stuck with the mapping problem. Nobody seems to be able to answer this common problem.
    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
  17. Are you running the example the webroot or have you installed it in a subfolder off the webroot?

    Comment by John Whish – May 28, 2010
  18. It is in the webroot and I tried in a subfolder. BUT I asked the hosting company about the problem and they were saying everything is fine. Now suddenly everything is working as it should. :) No word from the tech people of course. But I think the problem was that they have everything sandboxed and I was getting the error about access to CF Java objects was disabled. I think that was the problem. They had something turned off in my sandbox and then quickly switched it on and said "Nope, everything is fine." lol

    Comment by Don – May 28, 2010
  19. Ah! Ok, that would make a lot of sense :) Thanks for the update.

    Comment by John Whish – May 28, 2010
  20. I just got in touch with ORM lately and was experiencing the same issue.
    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
  21. @Sebastian, you can't have folders with dots in the names if you want to use components (cfcs) as the path to them is always in "package" syntax.

    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
  22. Ok, I already thought, that there's no alternative to the package syntax. Non-the-less creating a mapping solves the problem. Though it seems a bit strange to me, because I tried the following:

    <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
  23. Yes, EntityLoad uses hibernate. ColdFusion checks through the cfcs, when the application loads and caches the paths to them, so you don't need to use a path with any of the ORM functions, although each persisted entity must have a unique name.

    Comment by John Whish – August 24, 2010
  24. Okay, I've given ORM a shot and found it is not worth the hassle. Sorry, but the headaches involved far outweigh the benefits. I already have everything it can do easily for me written in CFCs so don't need it for that. The more complex queries are even harder in ORM so no sense going into it to do those.

    Nice thought tho.

    Comment by Don – August 24, 2010
  25. @Don, it really is worth working through the pain and learning curve as hibernate is really impressive. Being on a shared host may be adding to your problems.

    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

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.