28 January 2010 08:32 AM
15 Comments
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:- Start a Hibernate Session
- Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
- set the property 'Bar' on foo
- Start a Hibernate Transaction, which is tied to the Hibernate Session
- Tell the Hibernate Session to save() our Entity foo
- Commit our Hibernate Transaction, which will flush the Hibernate Session
- 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:
- Start a Hibernate Session with the ColdFusion Request
- Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
- set the property 'Bar' on foo
- 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)
- Call EntitySave() on the now detached object foo
- Commit the Hibernate Transaction, and flush the Hibernate Session
- Flush the Hibernate Session, when the ColdFusion Request Ends
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:- Start a Hibernate Session with the ColdFusion Request
- Start the Hibernate Transaction
- Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
- set the property 'Bar' on foo
- Throw an exception, which causes the Transaction to rollback
- 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:
That all being said, what happens to those of us that want to work, or have an architecture that inclined towards our previous example:
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.
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.
Instead, what actually happens is:
- Start a Hibernate Session with the ColdFusion Request
- Start the Hibernate Transaction, which closed the above Session, and starts a new one
- Retrieve Entity 'Foo', with a key of 1, and assign it to a variable foo
- set the property 'Bar' on foo
- Throw an exception, which causes the Transaction to rollback
- The Transaction end closes the current Hibernate Session, and creates a new one
- The request ends, the Hibernate Session is flushed, and nothing gets saved to the database, as there is nothing in the current Hibernate Session
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.






