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.




Obviously only works with methods of ColdSpring-managed beans, but I've yet to have it be insufficient.
Transactions are not a persistence concern, they're a business concern. As such, DAOs shouldn't be transactional, your service layer should be.
<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?
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.
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. :)
<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. :(
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