Next: , Previous: Rules about Persistent Classes, Up: Tutorial

2.8 Using Transactions

Elephant uses the Berkeley DB Transactional Data Store. This means most destructive operations need to be protected by transactions. By default Elephant does this:

     * *auto-commit*
     => T

Most real applications will want to control their own transactions because you will want one or more read-modify-update operations to happen as an atomic unit. This is guaranteed by the use of a transaction, but auto commits will only protect each individual update irrespective of whether the read value has changed.

If, for some reason, you want to turn off an implicit transaction when no explicit transactions are in effect, you can do

     * (setq *auto-commit* nil)
     => NIL

but note you'll have to wrap many of your operations in transactions or they will fail.

All database operations within the course of a transaction are ACID: atomic, consistent, isolated, and durable, if the transaction succeeds. After starting a transaction, you commit it, unless something fails, in which case you abort. The most common reason (other than programmer error) for transaction failure is deadlocks, which the Sleepycat deadlock resolver can resolve by signalling an error to force a retry.

All of this is packaged up in with-transaction. It starts a new transaction, executes the body, then tries to commit the transaction. If anywhere along the way there is a deadlock, the transaction is aborted, and it attempts to retry (a fixed number of times) by re-executing the whole body.

     * (with-transaction ()
        (setf (slot1 foo) 123456789101112)
        (setf (slot2 foo) "onetwothree..."))
     => "onetwothree..."

You can manually start a transaction as follows:

     * (start-transaction)
     => implementation-dependent

When you're done modifying the DB, commit with

     * (commit-transaction)

or abort with

     * (abort-transaction)

with-transaction and start-transaction may be nested (e.g. child transactions are supported) but not interleaved. To interleave transactions you have to manually maintain the transaction handles. The persistent objects look for transactions in the *current-transaction* special.

     * (setq *current-transaction*
             (controller-transaction-begin store-controller ))

To commit:

     * (controller-transaction-commit store-controller *current-transaction*)

If for some reason (like db error) you decide to abort, you can do so via (controller-transaction-abort store-controller *current-transaction*).