A transaction ends either successfully through an explicit commit command, or unsuccessfully in any of a number of ways. The following are the ways to end a transaction:
The transaction may be committed through its
commit
member function:
Xaction.commit();
The commit operation is sent to the backend at the point where
the commit
call occurs. Any exceptions
generated by the database transaction will be thrown from here at
the latest. The only exceptions that may be generated by
Xaction
beyond this point are related to
incorrect handling of the transaction object, eg. if an attempt
is made to abort Xaction
after it has been
committed, or runtime errors such as memory running out.
As a consequence, any streams or cursors nested within the
transaction (to be discussed later) must have been closed before
the commit()
. To do otherwise could possibly
allow a transaction to be committed before all related actions
had completed. The library will throw an exception if any
streams are still open when the transaction is ended.
A transaction is aborted if it is destroyed without having been explicitly committed:
{ work Xaction(Conn, "DemoTransaction"); // (Queries) } // Xaction destroyed here
work *XactionP = new work(Conn, "DemoTransaction"); // (Queries) delete XactionP; // Xaction destroyed here
try { work Xaction(Conn, "DemoTransaction"); // (Queries) Xaction.commit(); // If we get here, Xaction is committed } catch (...) { // If we get here, Xaction has been rolled back }
No matter where exactly the decision to abort is made, the actual abort operation is sent to the backend when the transaction's destructor is called. If the abort fails, eg. because the network connection has been lost, no error is reported [2] and the transaction will die of natural causes (either it has been closed by the backend already, or it soon will be if the connection is lost).
If a database error occurs during the transaction, such as an SQL syntax error or lost connection to the backend, the transaction is aborted.
work Xaction(Conn, "DemoTransaction"); try { // (Queries) Xaction.exec("SELECT !?^H^H^H^H"); // Fails: SQL syntax error } catch (...) { } Xaction.commit(); // ERROR: Xaction has already aborted!
For this reason, it is recommended always to include the "commit"
operation inside the try
block (if any)
surrounding the transaction code, not after
the catch
block.
Think of it as a natural extension of structural programming: the
transaction is "nested" within the connection, and the
transaction code can be "nested" in a
try
/catch
block.
No more queries may be issued to the transaction regardless of how it ended; an exception will be thrown if the application attempts to continue the transaction after that time. Ending a transaction more than once is an error, except that aborting it multiple times is tolerated to facilitate error handling.
[2] Throwing an exception from a destructor to report the error would have serious effects on program correctness. Never throw exceptions from a destructor.