Transactions Example

In Secondary Database Example we updated the MyDbEnv example class to support secondary databases. We will now update it to support opening environments and databases such that transactions can be used. We will then update ExampleDatabasePut to transactionally protect its database writes.

Note that we will not update ExampleInventoryRead in this example. That application only performs single-threaded reads and there is nothing to be gained by transactionally protecting those reads.

Example 7.1 Transaction Management with MyDbEnv

All of updates to MyDbEnv are performed in the MyDbEnv.setup(). What we do is determine if the environment is open for write access. If it is, then we open our databases to support transactions. Doing this is required if transactions are to be used with them. Once the databases are configured to supported transactions, then autocommit is automatically used to perform the database opens from within a transactions. This, in turn, allows subsequent operations performed on those databases to use transactions.

Note that we could have chosen to open all our databases with a single transaction, but autocommit is the easiest way for us to enable transactional usage of our databases.

In other words, the only thing we have to do here is enable transactions for our environment, and then we enable transactions for our databases.

    public void setup(File envHome, boolean readOnly)
        throws DatabaseException {
        
        EnvironmentConfig myEnvConfig = new EnvironmentConfig();
        DatabaseConfig myDbConfig = new DatabaseConfig();
        SecondaryConfig mySecConfig = new SecondaryConfig();

        // If the environment is read-only, then
        // make the databases read-only too.
        myEnvConfig.setReadOnly(readOnly);
        myDbConfig.setReadOnly(readOnly);
        mySecConfig.setReadOnly(readOnly);
                                                                                                                                  
        // If the environment is opened for write, then we want to be
        // able to create the environment and databases if
        // they do not exist.
        myEnvConfig.setAllowCreate(!readOnly);
        myDbConfig.setAllowCreate(!readOnly);
        mySecConfig.setAllowCreate(!readOnly);
                                                                                                                                  
        // Allow transactions if we are writing to the database
        myEnvConfig.setTransactional(!readOnly);
        myDbConfig.setTransactional(!readOnly);
        mySecConfig.setTransactional(!readOnly); 

This completes our update to MyDbEnv. Again, you can see the complete implementation for this class:

JE_HOME/examples/com/sleepycat/examples/je/gettingStarted/examples/
    MyDbEnv.java 

where JE_HOME is the location where you placed your JE distribution.

Next we want to take advantage of transactions when we load our inventory and vendor databases. To do this, we have to modify ExampleDatabasePut to use transactions with our database puts.

Example 7.2 Using Transactions in ExampleDatabasePut

We start by importing the requisite new class:

package com.sleepycat.examples.je.gettingStarted;
import java.io.File;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.util.ArrayList;
                                                                                                                                  
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Transaction;
                                                                                                                                  
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.tuple.TupleBinding;

In this example, we choose to allow ExampleDatabasePut.loadVendorsDb() to use autocommit to transactionally protect each record that we put into the database. What this means is, we do not actually have to change ExampleDatabasePut.loadVendorsDb() because the simple action of enabling transactions for that database is enough to cause autocommit to be used for all modifications to the database that do not explicitly provide a Transaction object.

For our inventory data, however, we want to load everything inside a single transaction. This means we need to explicitly commit the transaction when we get done loading our data, we also have to explicitly abort the transaction in the event of an error:

    private void loadInventoryDb() 
        throws DatabaseException {

        // loadFile opens a flat-text file that contains our data
        // and loads it into a list for us to work with. The integer
        // parameter represents the number of fields expected in the
        // file.
        ArrayList inventoryArray = loadFile(inventoryFile, 6);
                                                                                                                                  
        // Now load the data into the database. The item's sku is the
        // key, and the data is an Inventory class object.
                                                                                                                                  
        // Need a tuple binding for the Inventory class.
        TupleBinding inventoryBinding = new InventoryBinding();
                                                                                                                                  
        // Start a transaction. All inventory items get loaded using a
        // single transaction.
        Transaction txn = myDbEnv.getEnv().beginTransaction(null, null);
                                                                                                                                  
        for (int i = 0; i < inventoryArray.size(); i++) {
            String[] sArray = (String[])inventoryArray.get(i);
            String sku = sArray[1];
            theKey = new DatabaseEntry(sku.getBytes("UTF-8"));
                                                                                                                                  
            Inventory theInventory = new Inventory();
            theInventory.setItemName(sArray[0]);
            theInventory.setSku(sArray[1]);
            theInventory.setVendorPrice((new Float(sArray[2])).floatValue());
            theInventory.setVendorInventory(
                            (new Integer(sArray[3])).intValue());
            theInventory.setCategory(sArray[4]);
            theInventory.setVendor(sArray[5]);
                                                                                                                                  
            // Place the Vendor object on the DatabaseEntry object using our
            // the tuple binding we implemented in InventoryBinding.java
            inventoryBinding.objectToEntry(theInventory, theData);
                                                                                                                                  
            // Put it in the database. Note that this causes our 
            // secondary database to be automatically updated for us.
            try {
                myDbEnv.getInventoryDB().put(txn, theKey, theData); 
            } catch (DatabaseException dbe) {
                System.out.println("Error putting entry " + sku.getBytes());
                txn.abort();
                throw dbe;
            }
        }
        // Commit the transaction. The data is now safely written to the 
        // inventory database.
        txn.commit();
    }