Some thoughts on ColdFusion 9 ORM and Persistent CFCs
If you follow me on Twitter you may have recently seen me mention "Project Picard" a few times. While I still can't publicly talk about what it entails, I'm hoping to share what I've learned while working on it. Picard makes use of ColdFusion 9 and ORM. While I've played a bit with ORM (check out my demo content management system here) this current project is definitely much larger and more complex than anything I've done with ORM before. I recently ran into a problem that I want to share. Please remember this is new to me and that I might not do the best job explaining it. I do think it is something other people will run into so I wanted to share my findings as soon as possible.
So what's the problem? This issue came about when I stored an entity (an entity is just another way of saying a persistent CFC, and yes, I'll probably say CFC and entity interchangeably) in the Session scope. Everything with this particular CFC worked fine while testing, but once I actually stored the entity I ran into odd errors.
The entity was a User component. I'll share the code here just so you can see what it looks like.
2
3 property name="id" generator="native" ormtype="integer" fieldtype="id";
4
5 property name="usertype" fieldType="many-to-one" cfc="usertype" fkcolumn="typeidfk";
6 property name="userstatus" fieldType="many-to-one" cfc="userstatus" fkcolumn="statusidfk";
7
8 property name="guid" ormtype="string";
9 property name="email" ormtype="string";
10 property name="nickname" ormtype="string";
11 property name="firstname" ormtype="string";
12 property name="lastname" ormtype="string";
13
14}
There isn't much to this component yet. But make note of the of the many-to-one fields. Those were a late addition to my code base. After I added them and tried to use on, in this case, usertype, I got an odd error:
Message could not initialize proxy - no Session
Detail
Extended Info
Tag Context E (-1)
E (-1)
I was so confused by the E(-1) that I missed the clue as to what was truly going wrong here: "no Session."
What happened here is rather simple if you understand two basic concepts. First off, Hibernate has a concept of session. Do not confuse this with ColdFusion's Session scope. I think it's best to think of the Hibernate session much like a ColdFusion Request scope instance. The Hibernate session represent the current request and handles all data manipulation. If you - for example, ask for an object and then change it, the session will know this and will handle the updates. As another example, if you ask for the same object twice in one request, Hibernate is smart enough to know it doesn't have to go back to the database. Please read Mark Mandel's excellent blog post on this for more details. (Explaining Hibernate Sessions)
So why do we care? Hibernate tries its best to be as lazy as possible. As we are all good programmers, we know that laziness is simply a way to be as efficient as possible. In this case - notice the many-to-one relationships? Hibernate said to itself, "Hey, this requires me to get more data and create more objects, and you know what, Ray may not even use them. So I'll wait till he asks for them."
This efficiency though is exactly what bit me in the rear. On user login I had asked Hibernate for the User entity. I copied this to my Session scope. On a later request I looked at the related property userType, and since Hibernate had never loaded this I got the error. The "no Session" message basically meant that Hibernate no longer knew how to deal with it and simply gave up.
I spoke with Rupesh of Adobe on this and with his help was able to come up with two "rules" you may want to keep in mind.
1) When running a getWhatever on a entity persisted, any property that is lazy loaded will throw an error. I can get around this a few ways.
a) Use entityMerge. This forces the entity back into the current Hibernate session.
b) Disable the lazy loading. This is what I did for Picard. I just added lazy="false" to the two properties.
c) My least favorite option - before storing the entity in the Session, simply load the related properties. For example:
2ray.getUserType();
3session.user = ray;
Notice I run getUserType but don't actually store the result.
2) If I actually want to change the entity stored in the persistent scope I should entityMerge the component before running entitySave. In theory one could build a method in User.cfc to wrap this for you. That way I could do session.user.save() and it would merge itself and entitySave itself as well.
I hope this makes sense. If not, please let me know. So many of us are used to storing are CFCs in the persistent scopes, I can definitely see running into this issue if we aren't careful.

Here are some blog posts having to do with CF9 ORM and inheritance:
http://www.bennadel.com/blog/1688-learning-coldfus...
http://www.silverwareconsulting.com/index.cfm/2009...
This is exactly what I figured a lot of CF developers are going to do with Hibernate ;o)
This is why it is important to also understand Hibernate Object State when working with CF9 ORM.
I have another article here:
http://www.compoundtheory.com/?action=displayPost&...
That attempts to explain it.
But the short version is - when you put the object in CF Session, and go to another request, it becomes _detached_. Which means it no longer has access to any Hibernate Session, and thefore, can't lazy load (or do quite a few things).
To get it to work again, you have to move it back to a _persistent_ state, and there are several strategies for doing this. Check out my blog post for more details.
Generally speaking, its best to avoid detached objects whenever you can.
Check out fetch joins:
http://docs.jboss.org/hibernate/stable/core/refere...
So in this case it would be something like:
select user from User as user inner join fetch user.usertype inner join fetch user.userstatus
(On a site note, why 'usertype' and 'userstatus', the 'user' portion is kinda redundant, as it's on the User objects anyway... but that's just me being nitpicky ;o) )
I don't think this belong on EntityGet at all.
When you start getting into complex object graphs, saying 'grab everything', could potentially grab your whole database. Eek!
Really, in Hibernate, get()/EntityLoad() is for simple operations operations, using the default configuration.
When you start wanting to do more complicated things, on a sepcific basis, that's when the power of HQL (and also Criteria) queries comes to the fore, as you have full power over fetching strategies, and a variety of other options.
You have two options - (1) that is simple and straight forward and (2) that gives you full control over what is going on. Why do you want something in between?
Not a lack of complexity in regards to the framework.
It's like saying 'I only need to put one value in a distributed cache that is shared across a cluster. It's only 1 value, that should be a simple thing to implement, right?'
You're basically changing fetching strategies, which if done incorrectly, or in the wrong place in an appliaction can be a pretty big issue. Having flag that simply switches fetching strategies all over the place is a pretty bad idea, as it will get really abused in some bad ways.
Lets also not forget there are a slew of other (possibly better) options to solve this problem, some being:
1) just storing the id of the user in session, and retrieving that object as required
2) re-retrieving the object from Hibernate all over again when requested, so that you know the data is completely valid.
(I expound on those approaches some more in my linked article)
This is why dealing with detached objects is generally not a good way to go, it adds a large amount of complexity and management, with not a lot of gain.
Maybe I wasn't clear.
You're essentially doing 2 things, that are actually complex:
1) You want to change fetching strategies at runtime
2) You want to interact with a detached object
These two things are actually kinda tricky, esp. with Hibernate.
Hence the options I've outlined above for alternate strategies for when you want to store an Entity in the CF Session scope.
Does that make more sense?
And you can. With HQL or Criteria Queries.
So use them ;)
see #1: Immediate fetching
:)
Or if you MUST use session scopes (almost always a bad idea), then get the object, get the three values you wanted, and persist those.
I almost always design using client variables, which means that I usually won't have to change a thing the second I decide to hang another server out there for load-balancing and fail-over. Sticky-sessions won't wash either, as you lose both load-balancing and fail-over capabilities when you do so. Have a server go down, or take it down manually to do an upgrade, and the user's bound session goes poof.
And if your argument is still that you're only going to have a few dozen users at a time, then the exceedingly minor database hits a dozen users are going to cause to support client-based variables are equally meaningless... and you've still bullet-proofed your future.
As for entityMerge being smart enough - I have no idea what would happen if you changed some prop in another session (note lowercase), and then merged it with Session.User. I'd always assume the 'freshest' copy will "win". I'll do a test later this morning.
Worse, you're maintaining them for the entire duration of the page request as opposed to relegating their use inside a single function that obtains what it needs and then drops and frees the object immediately after the function exits.
As to session replication: "When a cluster uses session replication, session data is copied to other servers in the cluster each time it is modified. This can degrade performance if you store a significant amount of information in session scope. If you plan to store a significant amount of information in session scope, consider storing this information in client variables saved in a database."
It also gets worse the more servers you have in your cluster, as session traffic goes up exponentially.
Plus session replication is another one of those $7,500 a copy Enterprise-only features.
No thanks. For the cost of two CFE licenses I can buy two hardware-based LBs and run one with another in warm standby. Those will support, oh, a thousand or so servers, easy.
BTW, if entityMerge hits the database again to "refresh" the object, then there's really no reason whatsoever to persist the darn thing, is there?
As it stands, ORM entities live for an entire page request as that's how long the Hibernate session will live. You can force it to close early, but by default it is going to equal the CF Request.
I guess I'm not saying you are wrong per se - but it almost sounds as if you think one can't use ORM at all. I think like anything it is perfectly fine as long as you know what you are doing.