Transactionally Protecting Container Operations

To transactionally protect one or more container operations, do the following:

  1. Open your environment and containers such that they support transactions, as described in the previous section.

  2. Create an XmlTransaction object. These objects are created using XmlManager::createTransaction().

  3. Perform your operations, handing the XmlTransaction object to each container read and write method that is participating in the transaction.

    Be aware that you can use the same XmlTransaction for read and write operations performed on different containers and databases, provided that those containers and databases all exist in the same environment; there is no limit to the number of containers and databases that can participate in the transaction.

  4. When you have performed all of your transaction's operations, call XmlManager::commit() to commit the transaction. This causes the write operations performed under the protection of the transaction to be written to the files backing your containers and databases on disk.

    Once committed, the XmlTransaction object is no longer valid. That is, you cannot reuse it. If you want to perform another transaction, you must instantiate another XmlTransaction object.

  5. If the operations participating in your transaction should throw an exception or otherwise indicate an operational failure, terminate the transaction by calling XmlManager::abort(). This causes all of the write operations performed under the control of the transaction to be discarded.

    As is the case with XmlManager::commit(), an aborted XmlTransaction object is no longer valid and can not be reused.

Note

Note that when you create an XmlTransaction object, you can create a transaction based on an existing DbEnv object. If you do this, then the following rules apply:

  • Any handle for a transaction object can commit or abort that transaction. Once committed or aborted, all handles to the transaction are no longer valid.

  • If the XmlTransaction object goes out of scope without being committed or aborted, then the external DbEnv object that was used to create it is still valid and the underlying transaction is still active (until such a time as the transaction is either committed or aborted in some other location in your code).

  • Likewise, if the parent DbEnv object goes out scope while the XmlTransaction object is still active, then the underlying transaction is still active until such a time as the XmlTransaction object calls either commit or abort.

  • If all XmlTransaction objects go out of scope and there are no in-scope DbEnv objects, then the underlying transaction is automatically aborted.

Note

Never perform both transactional and non-transactional writes on the same container. Doing so can cause your underlying databases to no longer be recoverable in the event that recovery is needed.

If you open a container transactionally, and you do not provide a transaction for your write operations, BDB XML will automatically transactionally protect that write for you. However, if you perform writes to a transactional container, close the container and then open it without transactional support, then the writes to that container are not protected by a transaction.

The following provides an example of how to create, use, commit, and abort a transaction:

#include "DbXml.hpp"
...

using namespace DbXml;
int main(void)
{
    ...
    // Environment, manager, and container opens omitted
    // for brevity
    ...

    std::string file1 = "doc1.xml";
    std::string file2 = "doc2.xml";
    XmlTransaction txn;
    try {
        txn = myManager.createTransaction();
        // Need an update context for the put.
        XmlUpdateContext theContext = myManager.createUpdateContext();

        // Get the first input stream.
        XmlInputStream *theStream = 
            myManager.createLocalFileInputStream(file1);
        // Put the first document
        myContainer.putDocument(txn,         // the transaction object
                                file1,       // The document's name
                                theStream,   // The actual document. 
                                theContext,  // The update context 
                                             // (required).
                                0);          // Put flags.

        // Get the second input stream.
        theStream = myManager.createLocalFileInputStream(file2);
        // Put the second document
        myContainer.putDocument(txn,         // the transaction object
                                file2,       // The document's name
                                theStream,   // The actual document. 
                                theContext,  // The update context 
                                             // (required).
                                0);          // Put flags.
        // Finished. Now commit the transaction.
        txn.commit();

    } catch(XmlException &e) {
        std::cerr << "Error in transaction: "
                  << e.what() << "\n"
                  << "Aborting." << std::endl;
        txn.abort();
    } 
}