x::sql::transaction

x::sql::transaction inseparably glues an SQL transaction with an application scope:

void move_money(const x::sql::connection &conn,
                int from_account, int to_account, double amount)
{
    x::sql::transaction tran(conn);

    auto statement=conn->prepare("INSERT INTO ledger(account_id, amount) VALUES(?, ?)");
    conn->execute(from_account, -amount);
    conn->execute(to_account, amount);

    tran.commit_work();
}

Instantiate an x::sql::transaction on a thread's stack. x::sql::transaction's constructor calls begin_work(). x::sql::transaction's commit_work() and rollback_work() must be invoked instead of the connection object's. If the x::sql::transaction goes out of scope and gets destroyed for any reason, including an exception, without having its commit_work() or rollback_work() explicitly called, its destructor invokes rollback_work() automatically.

Using an x::sql::transaction results in an automatic transaction rollback when an uncaught exception gets thrown within the transaction's scope. The most common approach is to have x::sql::transaction's commit_work() invoked explicitly, just before the object goes out of scope and gets destroyed; or before leaving via some other exit path. The destructor automatically invokes rollback_work() when the execution thread leaves the scope via an exception, or any other means. It's also possible to invoke rollback_work() explicitly. The only advantage to an explicit rollback_work() is that a database error in the rollback throws an explicit exception. All exceptions are caught, logged, and discarded when rollback_work() gets invoked from the destructor.

However, throwing an explicit exception from rollback_work() is just a small consolation. Since the database connection's state is no longer deterministic, any database error occuring in commit_work() or rollback_work() will automatically close the connection, making it no longer usable.