Adam Tuttle

ColdFusion and ORMReload()

If you use ColdFusion's ORM (layer on top of Hibernate), then chances are pretty good you've needed to run an ormReload() now and then to pick up changes to your entity definitions. In our application, I've been having a minor nuisance getting this to work as intended, and I think I've finally figured out why.

Let's consider my ORM settings:

this.ormenabled = true;
this.ormsettings = {
    dbcreate="none"
    , logsql=false
    , cfclocation= ["/orm"]
    , flushAtRequestEnd=false
    , useDBForMapping=false
    , dialect="MySQL"
};
if (getEnvironment() eq "dev"){
    this.ormsettings.dbcreate = "update";
}

The intention is, on all environments other than my local machine, to block ormReload() from doing its job. In those scenarios, we're willing to jump through a few extra hoops to deploy our changes for the sake of safety; but during DEV you just want to be able to make changes and have them quickly picked up.

The only problem was that getting ORM to detect changes and actually do the ormReload() seemed to always require a CF service restart. It happens so infrequently that it was hard to remember if it requires a service restart every time, or just seemingly every time — but just often enough to be annoying.

As it turns out (I think), this bit of cleverness seems to have been too clever for Adobe ColdFusion. (I've not tested on Lucee.) It appears as though the very first time the value for this.ormsettings.dbcreate is set is the only value that is ever used. So, in the above case, where we default it to "none" but then overwrite it to "update" on development machines, ACF never noticed — or never cared that it changed. It would also seem that it wasn't the service restart + ormReload() that caused my changes to be picked up, but just the service restart itself.

It took explaining this frustration to our new developer, with the code in front of my face, to have my lightbulb moment.

I've since changed the code to the following:

this.ormenabled = true;
this.ormsettings = {
    dbcreate=(getEnvironment() eq "dev" ? "update" : "none")
    , logsql=false
    , cfclocation= ["/orm"]
    , flushAtRequestEnd=false
    , useDBForMapping=false
    , dialect="MySQL"
};

And now everything is peachy. Does this account mirror your own experiences, or am I losing my marbles?

Published 2015-03-16 @ 02:30 in ColdFusion

ORM and Transactions in ColdFusion

This has been written about a few times already by others in the community, but it still seems to be something that many people don't understand, so I thought I would put my oar in too. Truth be told, I thought I understood it but recently I learned that I still had a few details wrong.

ColdFusion's ORM (Hibernate) does not commit your changes to the database until the end of the request, by default.

That's an important thing to know, because there are other control-flow directives, like locking, that might lull you into a false sense of security if you're using the default behavior, and it could end up causing you grief.

Here's the ORM settings right out of an application I've been working on lately:

this.ormsettings = {
    dbcreate="update"
    , logsql=false
    , cfclocation= ["/orm"]
    , flushAtRequestEnd=false
    , useDBForMapping=false
    , dialect="MySQL"
};

Please note the line flushAtRequestEnd=false. This is explicitly disabling the default behavior I described above. When you do this, you have to tell Hibernate when to commit ("flush") the changes ("session") to the database. There are two ways to accomplish this: Either follow every call to entitySave() with a call to ormFlush(), or wrap your changes in a transaction.

The former approach bugs me, because I feel like I'm required to call two functions to do one thing. I know that the transaction approach does exactly the same thing, but I still feel better looking at transactions in my code.

Let's consider some code:

list = entityLoadByPK("List", 11);
list.setName("my new name");
transaction {
    entitySave( list );
}

On which line is the list name change committed to the database? If you said line 4, I have some bad news for you. It's line 3. To further illustrate this point, consider this slight modification to the same code:

list = entityLoadByPK("List", 11);
list.setName("my new name");
transaction {}

If you run this code, you'll see that your list's new name has been updated in the database after line 3 — and we never called entitySave() on it.

ORM is flushed at every transaction boundary. That means as you enter a new transaction, or exit a transaction, all unsaved ORM changes will be flushed to the database. This is in stark contrast to the way that transactions work with standard database queries using the <cfquery> tag or the queryExecute() method.

The only way you'll be able to roll-back ORM-based changes using transactions is to make the changes and issue the rollback all within the same transaction:

x = entityLoadByPK("List", 11);
transaction {
    x.setName("this change will be rolled back");
    transaction action="rollback";
}

As a rule of thumb, partially just to keep things tidy, we always load the entity within the transaction, too:

transaction {
    x = entityLoadByPK("List", 11);
    x.setName("this change will be rolled back");
    transaction action="rollback";
}

As ever, the best resource for ORM information in ColdFusion is John Whish's book ColdFusion ORM.

Published 2015-03-05 @ 02:06 in ColdFusion

Elvis Is (Back) in Production

Remember how I gave Adobe a really hard time about their fumbling of the Elvis operator (among other things) in ColdFusion 11 Update 3?

Today they released Update 4 in the stable update channel. Of course, there were two pre-releases of this patch, so if you were willing to run beta software in production, then you could already be ahead of the game on this one. As a policy, we don't run pre-release platform patches in production.

I wish it would have been released in the stable update channel sooner. The last pre-release refresh was on January 27th. That's 3 weeks and 2 days ago. I think 2 weeks is a pretty good testing period. But, it's been released, and it works —I tested it pretty thoroughly, at least as far as the Elvis operator is concerned. I've already installed it on our production server. So I won't give them too much grief over the timing.

If you're wondering why I cared so much about the Elvis operator, I present to you a sample, in the form of a diff of the awesome terseness that is ?:. These are just a few of the changes that I had sitting in a branch waiting for this day.

What I do think they still could have done better is the updating process, though. It's very evident that Adobe haven't figured out how to use the updater in situations like this.

When I refreshed from the first pre-release to the second, I had to uninstall and re-download the patch. Granted, it was point-and-click through the updater, but... isn't the whole point of having the updater to prevent the tedium of steps like "uninstall, re-download"? I would seriously consider the folks at Adobe read up on semver. The updater should be able to handle all of these situations flawlessly, without the need for manual deletion and re-downloading.

v11.0.4-beta1 is, by semver definition, less than v11.0.4-beta2. And v11.0.4 is by semver definition, greater than both of them. The updater should see all of these version numbers, regardless of which channel was used to install whatever updates are currently installed, and know how to get from point A to point B, with a single "upgrade" button.

Instead, you must:

Re-download

  • Go to the Installed Updates tab, and click the Uninstall button
  • Wait for the service to restart
  • Go to the Settings tab, and click the Restore Default URL button (thank god we got them to add that)
  • Restart the service manually. This isn't listed as a required step, but I always do it because I don't trust them to get it right without a server restart.
  • Log back in, go back to the Server Updates section, Available Updates tab, and click the Re-download button, because apparently the new update looks like the old one to the updater and it thinks you already have the stable release. You don't.
  • After it downloads, click the Install button, and wait for the service to restart

Is this better than the old process? Sure. Is it "good" yet? Nope.

Published 2015-02-19 @ 11:37 in ColdFusion

ColdFusion 11 Member Functions: Where They Got It Wrong

One of the things that Adobe put into ColdFusion 11 that I actually like (*coughs in the general direction of cfclient*) is member functions.

With them, this perfectly functional —if a bit inside out— line of code:

dateTimeFormat( dateAdd( 'd', -5, now() ), 'yyyy-mm-dd' );

... can be rewritten as the much more readable:

now().add( 'd', -5 ).dateTimeFormat( 'yyyy-mm-dd' );

At a glance, I personally enjoy reading the latter one more than the former. It's easier to read left-to right rather than unpacking sets of parenthesis mentally.

But there's still one problem here: .dateTimeFormat() Why not just .format()? Probably because they support all three non-member-function variants: someDate.dateFormat(), someDate.timeFormat() and someDate.dateTimeFormat().

Maybe this doesn't sound completely crazy to you. Fair enough. What if I tell you that, aside from List functions (which get a pass because lists aren't actually a type, they're just strings with some extra parsing rules implied) these three member functions are the only member functions that include their type prefix? Here's the list of all new member functions so you can check for yourself. Now do you think it's an anomaly that's worth looking at?

How can we fix this? By realizing that we don't need all three. The new dateTimeFormat() function added in CF11 uses slightly modified masking from its dateFormat and timeFormat predecessors to allow it to do everything they can do, only better. And since dateTimeFormat() is new in CF11 (just like member functions), it makes a good amount of sense to rip this band-aid off now, while we still can. If we wait any longer, someDate.dateFormat() is going to end up in a lot of existing codebases, and we'll never be rid of it, because of backwards compatibility.

As Scott Stroz is fond of saying, "If only the meeting had lasted 5 more minutes..."

Some have called for deprecation of the dateFormat and timeFormat member functions. I disagree, but also think it's better than nothing. My opinion is that member functions are still new enough that it's ok to say, "Sorry, we screwed this up. Let's fix it before we make it any worse."

This has been logged as a bug: 3940802. Your votes and comments would be appreciated.

Published 2015-02-16 @ 05:07 in ColdFusion