zsamer wrote:
Ok, i see.
How can we fix this? What alternative is there for transactions in ORM?
Model classes do need to use transactions sometimes, because a Model operation may execute multiple SQL statements, and it needs to ensure that the whole logical operation is committed atomically.
But this is complicated because a Model can call another Model. Which Model gets to begin the transaction and commit it? Somehow each Model needs to be told whether it has the responsibility to start and finish its transaction, or if the calling code is handling that.
One approach is that no Models may call transaction methods. Always keep transaction control in the application layer. That way you have one point of control, and therefore you always know the state. Unfortunately, the default usage of the database adapter is that everything runs in autocommit mode, and a developer might not create an explicit transaction before calling a Model method that must be wrapped in a transaction.
I designed a Perl module for InterBase, circa 1996. In my module, a database Connection class was a factory for a Transaction object, and the Transaction was a factory for a SQL Statement object. That way you could pass the Transaction object to Model classes, and that would provide the context in which to run SQL Statements (note that InterBase permits multiple active transactions per connection, unlike most brands of RDBMS, so the context in which to run a Statement is important).
Any Model that is passed a Transaction must not finish that Transaction, but instead rely on the calling code to do it. Any Model that is _not_ passed a Transaction must begin one, pass that Transaction to any other Models it calls, and finish the Transaction after all the work is done.
This is complicated if the calling code may or may not have begun a transaction. Then the Model has a dilemma: assuming the calling code has begun a transaction is bad because if it hasn't then we're in autocommit mode, and the Model's work may be done in a non-atomic fashion. Assuming the calling code has _not_ begun a transaction is bad because if it has, then any transaction control the Model attempts is either a no-op or else gives an error.
So the solution is that the Model must know whether there's a transaction in progress, and behave accordingly. That's what's missing from the solution in Propel and in your library. The Zend_Db_Adapter class should have a method "isTransactionRunning()" or something like that, instead of maintaining a "level" counter as you have done. That way the Model can query the current state; this gives it the same information as in my Perl module where Transaction was a distinct class that was passed to a Model.
One further rule is that developers must use the beginTransaction(), commit(), and rollback() methods uniformly when they want explicit transactions. No fair bypassing them and executing query("COMMIT"), because then the the Adapter class can't track whether there's a transaction in progress or not (AFAIK most database API's don't have a method to check if a transaction is in progress).
Regards,
Bill Karwin