ColdFusion 9 ORM and Transactions – It Does Not Mean What You Think it Means.

In one of my favourite lines from the Princess Bride

[Vizzini has just cut the rope The Dread Pirate Roberts is climbing up]
Vizzini: HE DIDN'T FALL? INCONCEIVABLE.
Inigo Montoya: You keep using that word. I do not think it means what you think it means.

..and when using Transaction support with ColdFusion 9 ORM and Hibernate Sessions…It did not do what I thought it did. It did something else.

To give some back story: As we have discussed previously, a Hibernate Session is meant to flush at the end of a Hibernate marked Transaction.

So if we are talking to Hibernate natively (for example set up with JavaLoader), and had the following code:

mySession = sessionFactory.openSession();

foo = mySession.get("Foo", 1);
foo.setBar("fooBar");

trans = mySession.beginTransaction();

mySession.save(foo);

trans.commit();

mySession.close();

Which then translates to the following:

  1. Start a Hibernate Session
  2. Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
  3. set the property 'Bar' on foo
  4. Start a Hibernate Transaction, which is tied to the Hibernate Session
  5. Tell the Hibernate Session to save() our Entity foo
  6. Commit our Hibernate Transaction, which will flush the Hibernate Session
  7. Close the Hibernate Session

If we attempt to do the same thing in ColdFusion, it may look something like this:


foo = EntityLoad("Foo", 1, true);
foo.setBar("fooBar");

transation {
    EntitySave(foo);
}


Which you would think would work exactly the same as above. Except or one small thing – It Doesn't.

If you read the ColdFusion Documentation on ORM Transactions, it states:

When <cftransaction> begins, any existing
ORM session is flushed and closed, and a new ORM session is created.
The <cftransaction> can be committed or rolled
back using appropriate ColdFusion tags in <cftransaction>.
When the transaction ends and has not been committed or rolled back
explicitly, it is automatically committed and the ORM session is closed.
If there is any error inside the transaction, without any exception handling,
the transaction is rolled back.

This means that the above ColdFusion code, will actually perform like so:

  1. Start a Hibernate Session with the ColdFusion Request
  2. Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
  3. set the property 'Bar' on foo
  4. Hit the start of the Transaction, flush the current Hibernate Session, close it, and then start another one.  At this point, foo has been persisted to the database, and is now detached (and remember detached is generally bad)
  5. Call EntitySave() on the now detached object foo
  6. Commit the Hibernate Transaction, and flush the Hibernate Session
  7. Flush the Hibernate Session, when the ColdFusion Request Ends

Or if you want to look at it another way, it would be the equivalent of doing the following native communication with Hibernate:

mySession = sessionFactory.openSession();

foo = mySession.get("Foo", 1);
foo.setBar("fooBar");

//foo actually gets persisted to the DB at this point
mySession.flush();
mySession.close();
mySession = sessionFactory.openSession();

trans = mySession.beginTransaction();

//foo is now detached
mySession.save(foo);

trans.commit();

mySession.flush();
mySession.close();


The big question at this point, is of course – Well, why does ColdFusion do it this way then?

There is actually a very good reason, although that reason (imho) is based on a few assumptions. 

The first assumption, is that you are going to write your ORM interactions inside your transaction block, like so:


transation {
    foo = EntityLoad("Foo", 1, true);
    foo.setBar("fooBar");

    EntitySave(foo);
}


The second  assumption is that, as a developer you will leave the ORM setting flushatrequestend set to its default of true, and therefore a split between the Hibernate Sessions is required.

For example, if we had the following code:


transation {
    foo = EntityLoad("Foo", 1, true);
    foo.setBar("fooBar");

    myMethodHereThrowsAnException();

    EntitySave(foo);
}


If
we didn't have separate Hibernate Sessions, or at least clearing of the
Session after Hibernate Transaction rollback, , the following would
occur:

  1. Start a Hibernate Session with the ColdFusion Request
  2. Start the Hibernate Transaction
  3. Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
  4. set the property 'Bar' on foo
  5. Throw an exception, which causes the Transaction to rollback
  6. The request ends, the Hibernate Session is flushed, and foo gets saved back to the database, since it is still available in the Hibernate Session.
Which is very much against the nature of a rolled-back transaction.

Instead, what actually happens is:

  1. Start a Hibernate Session with the ColdFusion Request
  2. Start the Hibernate Transaction, which closed the above Session, and starts a new one
  3. Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
  4. set the property 'Bar' on foo
  5. Throw an exception, which causes the Transaction to rollback
  6. The Transaction end closes the current Hibernate Session, and creates a new one
  7. The request ends, the Hibernate Session is flushed, and nothing gets saved to the database, as there is nothing in the current Hibernate Session

Given the above assumptions, this is much better, since we want to have the Transaction actually rollback.

That
all being said, what happens to those of us that want to work, or have
an architecture that inclined towards our previous example:


foo = EntityLoad("Foo", 1, true);
foo.setBar("fooBar");

transation {
    EntitySave(foo);
}


I.e.
We want to have our Transaction interact with the current Hibernate
Transaction, and not flush at the end of the ColdFusion request, which,
I find to be the solution I prefer for when building applications with
Hibernate.

What can we do then?

There are two things that need to be done, if we want to do this kinda of approach. The first is, to set the ORM setting flushatrequestend
to false.  This means that the Hibernate session won't flush at the end
of the ColdFusion request, and we have complete control over when it
does flush.

To make sure the Transaction interacts with the current request, we can't use <cftransaction> or a transaction {} block, we have to natively interact with the Hibernate Session directly, which is quite straight forward.


foo = EntityLoad("Foo", 1, true);
foo.setBar("fooBar");

trans = getORMSession().beginTransaction();

try
{
    EntitySave(foo);

    trans.commit();
}
catch(Any exc)
{
    trans.rollback();
    rethrow;
}


We have to do the extra work to manage the commit and/or rollback of the Transaction manually, but this something that could be implemented with a UDF or using Aspect Oriented Programming for reuse.

Now I can use Transactions
to demarcate when I want the Hibernate Session to be flushed, and I'm
not tied to wrapping my entire Entity operations inside a <cftransaction> block.

This is something I really like about how Adobe implemented the ORM integration.  If I don't particularly
like the way an individual aspect has been implemented, I have direct
access to the underlying engine, and can therefore interact with it in
whatever way that suits the way I develop and thearchitecture of my application.

Now Transactions mean what I think they mean.

 
UPDATE 28/10/01:
I just wanted to add a small conclusion to this post, that sums what I am trying to get across simply.
 
Basically what this all boils down to is: If you are using <cftransaction> or a transaction{} block, make sure you are wrapping them around the entire operation, not just the EntitySave() portion at the end, as otherwise bad things can happen.

If you want to just wrap transactions around the EntitySave() part of your code, you will need to use native Hibernate Transactions like I have done above.
 
Hopefully that boils the large post above into a nice tasty nugget of knowledge that is far easier to swallow.

Leave a Comment

Comments

  • Rick O | January 28, 2010

    I’m fuzzy on why you *don’t* want to wrap a transaction block around the entire thing — either in ORM or non-ORM. Is there some special case where you wouldn’t want that?

    Without the block including the get(), you’d end up with race conditions. Luckily, Hibernate can detect such issues … but it still seems to me like you’re asking for trouble.

    (Or maybe I’m twitchy because I spent all day helping my students resolve merge conflicts because they couldn’t communicate with each other …)

  • Mark | January 28, 2010

    @Rick
    Three reasons as to why.
    (1) I’ve already seen a stack of people do just as I described and not realise the consequences
    (2) With a Controller + Service Layer, it is harder to wrap it all in a transaction, unless you want to use transactions in your controller (not my favourite thing)
    (3)I like to use AOP to wire transactions around my Service layer’s save() methods. This enables me to do this, and demarcate my Session flushes with Transactions.

    I don’t see how using a transaction block around just EntitySave() will incur race conditions. Can you elaborate?

  • Jaime Metcher | January 28, 2010

    Wow. This deserves to be in the official docs.

    Re the possibility of flushing the session after an exception + rollback, just want to confirm that you’ve actually seen this happen. In my experience (with Spring, not CF9 ORM) the session is closed on an exception for exactly this reason, and I’m intrigued that CF9 wouldn’t do the same thing.

    Quite apart from that, the session is bound to a transaction (ie by default autocommit is illegal), so once the transaction is hosed you actually can’t work with the session at all until you open a new transaction. Once again intrigued that CF9 would do it differently.

  • Mark | January 28, 2010

    @Jaime
    Maybe I wasn’t clear.
    The reason that CF9 closes and opens a new session around transactions is to *avoid* the scenario outlined above.

  • Ben Nadel | January 28, 2010

    Wow. This ORM stuff is very intimidating to me 🙂

  • Mark | January 28, 2010

    @Ben – it’s really not that crazy, you just really need to understand how Hibernate Sessions work.

    Once you wrap your head around that, it all becomes quite clear (at least in my mind).

  • Rick O | January 28, 2010

    Mark-

    In re race conditions, I was thinking of the simplistic, classical example:

    Alice hits the page and gets her copy of foo.
    Bob hits the page and gets his copy of foo.
    Alice hits the mutator.
    Bob hits the mutator.
    Alice saves her foo.
    Bob saves his foo … overwriting the changes Alice just saved.

    With Hibernate/CF9ORM, you can set up a Version or a Timestamp field to help mitigate these problems — when Bob tries to save, it’ll see that he’s trying to update an old version of the object and it’ll throw an error. (And rightly so.)

    But, that’s presuming that you’re using such constructs. Most people aren’t.

    Putting the entire block in a transaction is the database equivalent of a cflock — it makes the entire transaction atomic, including both the read and the write(s). You may still get an error, but you may not, depending on if the database thinks there’s a conflict in the data to be written. (And what your transaction isolation level is set to.)

    Putting a transaction around just the save only does something for you if your save involves multiple statements. A single statement is guaranteed to be atomic anyway, so it’s only when you’ve got multiple statements do transactions mean anything.

  • Mark | January 28, 2010

    But the transactional lock still occurs even if you only put it around the entitySave(), as the data is persisted to the database at the end of the Transaction (and sometimes insert data on EntitySave() is done right there and then).

    This means the whole SQL operation is still atomic, as the only part that happens outside of a transaction is possible SELECT requests, which if the INSERT/UPDATE/DELETE operations via ORM are also in transactions, will also be atomic.

    So I cant see how this would cause race conditions.

    If you are talking about issues around ‘Last one wins’ – that comes down to application locking, and making sure no 2 users can edit the same thing at the same time. This is an issue for any application, regardless of whether or no they use ORM.

  • Mark | January 28, 2010

    I just added a small ‘Conclusion’ to the blog post to attempt to explain my overall contention in this post. I hope it clears things up.

    Sorry if was convoluted, there was a lot of back theory to get through.

  • Justice | January 29, 2010

    Hibernate best practice is to wrap an entire session with a single transaction at the top level. So in ColdFusion, if you have a front controller, that means that the front controller should essentially be inside a transaction. Beyond that, there is no need to do transaction management.

    Additionally, why are people calling EntitySave() on entities that are already associated with the Hibernate session (ie with a row in the database)? That is an error. Thankfully Hibernate detects that situation and simply ignores the call to EntitySave rather than going on and doing the save, but still, people should not be doing that. It is very clear in the Hibernate docs that save is only to attach a transient entity to the session and make it persistent, and does not "save" the changes made to an object down to the associated database row.

    Cheers,
    Justice

  • Mark | January 29, 2010

    @Justice
    I think what you mean is ‘wrap the entire request in a transaction’, not ‘wrap an entire session in a transaction’, as the latter would be impossible.

    This is a practice that is advocated by a lot of the Spring crew, but not necessarily by Hibernate itself, and is also a subject of debate. So it is *an* solution, but not necessarily *the* solution. Different strokes for different folks, and all that jazz.

    Re: EntitySave() on persisted objects is also just fine. EntitySave() in CF will call a Session.saveOrUpdate(), which ignores persistent Entities. See (http://docs.jboss.org/hibernate/stable/core/reference/en/html/objectstate.html#objectstate-saveorupdate). This can be very useful when reusing application workflows, so you don’t need to adjust things if your Entity is persistent or not. Hibernate can automatically manage that for you. So it really isn’t ‘an error’, it’s a valid way of managing persistence, and a common code practice.

  • Justice | January 29, 2010

    @Mark,

    I apologize for being less than clear. I actually meant ‘wrap the entire contents of a Hibernate session in a transaction.’ In ColdFusion, this naturally translates to ‘keep a single Hibernate session open for each request and wrap the entire request in a transaction.’

    Doing this permits arbitrary services to take and return arbitrary domain objects with arbitrary relationships to other domain objects with arbitrary lazinesses, while maintaining strict separations between domain model, domain services, http controllers, and html view templates, and permits everything just to work.

    As to EntitySave, I’m not sure I see much of a use case yet. What kinds of application workflows do you have in mind that requires this behavior?

  • Mark | January 29, 2010

    @Justice
    Re: ‘wrap the entire contents of a Hibernate session in a transaction.’
    How can you wrap a Hibernate Session in a transaction, when you are required to start a Hibernate Session to start a Transaction? By the same nature, you can only commit/rollback the transaction before you close() a Hibernate transaction. So I am still confused by what you are trying to say.

    This article was not meant as a avenue to debate the various pro’s and cons of transaction management strategies with Hibernate. It’s purpose was to illuminate how Hibernate Sessions are managed with Transactions in CF9, which is not necessarily apparent, unless you read the documentation.

    I agree that wrapping a request in a transaction is a very viable solution, but it is only *one* solution to a problem that can be skinned multiple ways. If that is the solution you prefer, more power to you, but other solutions have merit as well.

    As to EntitySave() workflows. Simply CRUD workflows spring to mind. It would make sense to have the same workflows for create vs edit, as 9/10 times they tend to be identical except for if they are inserting or updating the database. Being able to apply EntitySave() arbitrary to whether or not the Entity is persisted allows you to greatly reuse your code base.

  • Luis Majano | February 27, 2010

    Mark,

    Upon further testing, this gets more complicated than it seems. If you do the beginTransaction() on the current session, it will begin a new transaction for the entire session, but if you nest it, it will just merge it. So basically, you cannot do a rollback for a single piece of atomical code, because every time you do ORMGetSession().beginTransaction(), it actually flushes the session also. Try it, I did and it behaves just like the normal CF transaction.

    So in all reality, you are doing the same behavior of the cftransaction tag. The only way to go around it would be to go completely java native and do native opening of the session, interact with that java session to load, persist etc (which means you loose all the nice abstraction level CF did) and then close it yourself.

    So the only approach I have found to meet my needs is to transaction the entire conversation first (at the request level), and then if I want a specific attomical operation with rollback, I use transaction action="setsavepoint" and use a direct rollback using the save points, so it only rollbacks certain units of work.

    try {
    transaction action="begin"{

    task1 = entityLoad(‘Task’,{taskID="8a96e6fe24554f50012458d7065200a1"},true);
    task1.setNotes(‘SHOULD UPDATE at #now()#’);

    try{
    transaction action="setsavepoint" savePoint="point2";

    // Do some work
    task = entityLoad(‘Task’,{subject="Test"},true);
    task.setNotes(‘SHOULD NOT UPDATE #now()#’);

    }
    catch (Any e) {
    transaction action="rollback" savepoint="point2";
    throw(e);
    }

    }

    transaction action="commit";

    }
    catch (Any e) {
    transaction action="rollback";
    throw(e);

    }

  • Nando | March 24, 2010

    A small correction to Barney’s excellent example and explanation

    trans = getORMSession().beginTransaction();

    should be

    trans = ORMGetSession().beginTransaction();