Thoughts on Adding Nested CFTRANSACTION Support to ColdFusion

I'd like to to make a case for the ability to nest CFTRANSACTION tags in ColdFusion. I think this would be incredibly useful, since not being able to nest transactions is becoming an increasing pain. I know this is probably wishful thinking, and that it might be a while before we see it (if ever). But I wanted to start a public discussion about it and get thoughts from others, as well as urge people to submit this a a feature request to Adobe using the request form at http://www.adobe.com/cfusion/mmform/index.cfm?name=wishform&product=7.

For me, I'd be perfectly happy if they just supported the ability to nest straighforward transaction blocks like this:

<cftransaction>
<cfset doSomethingThatContainsATransaction() />
<cfset doSomethingElseThatContainsATransaction() />
</cftransaction>

		

That's it. No changes to the the tag itself in terms of attributes or attribute values. Just the ability to nest them. If something fails anywhere in the child transactions, the whole thing would be rolled back. This would make using things like Transfer, Reactor, ColdSpring Advices, unit testing, and 3rd party components SO much easier.

The issue that some people seem to have with this are in the uncommon (at least to me) cases where someone is using savepoints. I personally have never written a savepoint transaction, and I don't know anyone who has. To me this just makes no sense. To roll back a transaction half way seems to fly in the face of using a transaction in the first place. However, I'm not going to dwell on this. If some people use these for whatever reason, then it should be taken into consideration.

My take would remain the same, in that a failure in the nested transactions would roll back the entire thing, regardless of whether the internal transactions used a savepoint or not. People using savepoints would either have to accept this as a consequence of transaction nesting, rework their design, or just not use nesting and leave things as they work now. I'd view this as a perfectly acceptable tradeoff since it makes things so much easier for the vast majority of people.

Another problem is when queries in nested transactions call stored procedures via EXEC that contain their own internal transactions. I'm not sure how to handle it exactly, but my opinion remains the same. Either these would throw an error when run inside nested cftransaction blocks, or they would roll back anyway if there was a failure in the nested transactions. Or again, they would just have to accept that they can't use nested transactions in their setup.

The bottom line is that it seems crazy to not add this when only a very small number of people would actually have problems with it. And even then, those people would still have the simple option of not nesting transactions. Nothing would change for them. But the majority would get an extremely useful feature.

Further, it leaves the door open for Adobe to make this the default behavior for nested transactions, with the ability for them to add more attributes or attribute values to the CFTRANSACTION tag in the future to let these people handle their less common situations.

If you agree that this would be useful to most CF developers, please let Adobe know using the feature request form. Please fire away with comments as well. I'm interested to see if most people seem to agree with me, or if my take on this is off the mark.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
My ColdSpring transactionadvice can handle this for you, as long as you're comfortable with giving over control of your transactions to an advice. It's behaviour is to make nested-transactions a no-op (that is, ignore them). In a nutshell, whenever it's about to start a transaction, it checks to see if it's already in a transactional context and if so, doesn't start a new one.

Obviously only works with methods of ColdSpring-managed beans, but I've yet to have it be insufficient.
# Posted By Barney | 1/11/08 2:43 PM
But Barney, if I have a method that in turn calls two methods, say on a data access object, and those each have transactions in them, this would just not do anything (no outer transaction), correct? What I'm saying is that I want the ability to say "if either of these two calls fail, I want them both to roll back". I don't see how your advice would do that. Am I missing something?
# Posted By Brian Kotek | 1/11/08 2:51 PM
If a method is transactional, it should be wrapped in the transaction advice, that will ensure that it is ONLY called within a transactional context. If it's called directly, it'll be in its own private transaction. If it's called by another non-transactional method, it'll be in its own private transaction. If its' called by another transactional method, it'll inherit the transaction of it's caller.

Transactions are not a persistence concern, they're a business concern. As such, DAOs shouldn't be transactional, your service layer should be.
# Posted By Barney | 1/11/08 2:58 PM
True, it was a bad example. Say my service does something like this:

<cffunction name="doSomething">
<cfset userGateway.updateAllUsers() />
<cfset trainingGateway.updateAllTraining() />
</cffunction>

Internally, those might be doing several queries, and they have transactions in them. However, I want to say I want BOTH to also be handled in a transaction, basically a higher level transaction:

<cffunction name="doSomething">
<cftransaction>
<cfset userGateway.updateAllUsers() />
<cfset trainingGateway.updateAllTraining() />
</cftransaction>
</cffunction>

Such that if something fails in updateAllTraining(), not only do those queries roll back, but everything in updateAllUsers() rolls back too. I'm not seeing how the Advice will handle that, even if the service and the gateways are all managed by ColdSpring and using the advice. In this case, the call to doSomething() would be seen by the advice as having a transaction (the try/catch in the Advice would fail because updateAllUsers() and updateAllTraining() have transactions in them), and so it would run doSomething() with no transaction around it. But then, a failure in udpateAllTraining() would not roll back updateAllUsers(). Correct?
# Posted By Brian Kotek | 1/11/08 3:37 PM
Wasn't this suppose to be in CF8? I seem to remember hearing it in the build up to the CF8 release, and thinking "About Time"!
# Posted By David Beale | 1/11/08 4:08 PM
Yes, I even blogged about it during the Scorpio beta tour. Until this week it was also listed as a feature on the CF8 features page at Adobe. It appears that there was a pretty major miscommunication about this, because it appears that nothing was actually done regarding this feature.
# Posted By Brian Kotek | 1/11/08 4:12 PM
in CF 7 nested transactions are not supported.
My workaround strategy for discarding need of nested transactions:
Lets say that i have a method X that requires to be run inside a transaction block. I just tell to the method that i am already in transaction (or not) and method will not created a new transaction (or it will created one).

<cfunction name="X">
<cfargument name="_IN_TRANSACTION_" required="no" default="false" type="boolean" hint="If you call this parameter inside a cftransaction block then set this to true"/>
<!--- other arguments --->
<cfif not arguments._IN_TRANSACTION_>
<cftransaction>
<cfset arguments._IN_TRANSACTION_>
<!--- recurse call to X but now we are in a transaction block --->
<cfinvoke method="X" argumentcollection="#arguments#">
</cftransaction>
<cfelse>
<!--- now we should be inside a cftransaction block --->
<!--- do the job that requires transaction --->
</cfif>
</cffunction>

So each time that i call the X method i check if i am already in a transaction or not and i pass the _IN_TRANSACTION_ as true or i don't pass it at all since it defaults to false.
Not the best mayby solution, but solved me a lot of time.
# Posted By Stratis A. | 1/13/08 10:55 AM
Correct me if I've misunderstood the situation, but if you ignore subsequent nested cftransactions once you're inside the main transaction block, aren't you in potential danger of having the inner blocks bleed over each other?

I thought one function of the cftransaction block to make that code atomic. If half of sub-transaction A finishes, then sub-transaction B executes, then A finishes, it all may have completed successfully within a larger transaction block, but A and B may have still hosed each other. Or can you assume that things will execute synchronously and not cause problems?

Maybe I misunderstood something but I thought I'd toss that out. :)
# Posted By Ben Burwick | 1/15/08 10:21 AM
Funny finding this post after I just wrote my first SAVEPOINT transaction. I'll give you an example of what I did for some ideas. I have members in my system and sometimes they create duplicate accounts. We want to merge these accounts together down to a single member ID but there are sometimes collisions, in which case we keep the "target" members data and delete the "source" member data. It looks like this:

<cfquery name="select">
select * from table
</cfquery>

<cfloop query="select">
<cftry>

<cfquery name="update">
SAVEPOINT foo;
UPDATE ...;
</cfquery>

<cfcatch type="any">

<cfquery name="delete">
ROLLBACK TO SAVEPOINT foo;
DELETE duplicate data...;
</cfquery>

</cfcatch>
</cftry>

</cfloop>

There are other ways of accomplishing the same thing, but SAVEPOINTs were perfect in this case because a collision is rare relative to the overall operation. I prefer this to doing a SELECT check before each update.

In my case, I came looking on Google for nested CFTRANSACTION support because I want to run this entire operation in a Unit test framework and at the end of the test, roll the whole thing back. I'm not sure how to accomplish this other than replace the normal "commit" with "rollback" temporarily while I'm developing. If I run this test afterwards, I'm going to lose all of my test data unfortunately. :(
# Posted By Brian | 6/27/08 8:40 PM
Maybe this is not related to the subject but maybe someone can clarify this issue for me .

In Adobe's CFTRANSACTION documentation for Coldfusion MX 7.02 Someone wrote:
TomGru said on Nov 13, 2007 at 4:22 AM :

Be aware that this

<CFTRANSACTION>
<CFLOOP query="qxy">
.... Insert something
</CFLOOP>
</CFTRANSACTION>

dosen't work and you wont get any error either...

this works....


<CFLOOP query="qxy">
<CFTRANSACTION>
.... Insert something
</CFTRANSACTION>
</CFLOOP>

Is this true or not?
Thank you in Advance.

EstebanD
# Posted By Esteban | 8/7/08 4:48 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner