com.lvl6.mobsters.dynamo.setup.TransactionExamples.java Source code

Java tutorial

Introduction

Here is the source code for com.lvl6.mobsters.dynamo.setup.TransactionExamples.java

Source

/**
 * Copyright 2013-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Amazon Software License (the "License"). 
 * You may not use this file except in compliance with the License. 
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/asl/
 *
 * or in the "license" file accompanying this file. This file is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express 
 * or implied. See the License for the specific language governing permissions 
 * and limitations under the License. 
 */
package com.lvl6.mobsters.dynamo.setup;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute;
import com.amazonaws.services.dynamodbv2.model.AttributeAction;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
import com.amazonaws.services.dynamodbv2.model.GetItemResult;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
import com.amazonaws.services.dynamodbv2.model.ReturnValue;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
import com.amazonaws.services.dynamodbv2.transactions.Transaction;
import com.amazonaws.services.dynamodbv2.transactions.TransactionManager;
import com.amazonaws.services.dynamodbv2.transactions.Transaction.IsolationLevel;
import com.amazonaws.services.dynamodbv2.transactions.exceptions.DuplicateRequestException;
import com.amazonaws.services.dynamodbv2.transactions.exceptions.InvalidRequestException;
import com.amazonaws.services.dynamodbv2.transactions.exceptions.ItemNotLockedException;
import com.amazonaws.services.dynamodbv2.transactions.exceptions.TransactionException;
import com.amazonaws.services.dynamodbv2.transactions.exceptions.TransactionRolledBackException;
import com.amazonaws.services.dynamodbv2.util.TableHelper;

/**
 * Demonstrates creating the required transactions tables, an example user table, and performs several transactions
 * to demonstrate the best practices for using this library.
 * 
 * To use this library, you will need to fill in the AwsCredentials.properties file with credentials for your account, 
 * or otherwise modify this file to inject your credentials in another way (such as by using IAM Roles for EC2) 
 */
public class TransactionExamples {

    protected static final String TX_TABLE_NAME = "Transactions";
    protected static final String TX_IMAGES_TABLE_NAME = "TransactionImages";
    protected static final String EXAMPLE_TABLE_NAME = "TransactionExamples";
    protected static final String EXAMPLE_TABLE_HASH_KEY = "ItemId";
    protected static final String DYNAMODB_ENDPOINT = "http://dynamodb.us-west-2.amazonaws.com";

    protected final AmazonDynamoDBClient dynamodb;
    protected final TransactionManager txManager;

    public static void main(String[] args) {
        print("Running DynamoDB transaction examples");
        try {
            new TransactionExamples().run();
            print("Exiting normally");
        } catch (Throwable t) {
            System.err.println("Uncaught exception:" + t);
            t.printStackTrace(System.err);
        }
    }

    public TransactionExamples() {
        AWSCredentials credentials;
        try {
            credentials = new PropertiesCredentials(
                    TransactionExamples.class.getResourceAsStream("AwsCredentials.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        dynamodb = new AmazonDynamoDBClient(credentials);
        dynamodb.setEndpoint(DYNAMODB_ENDPOINT);
        txManager = new TransactionManager(dynamodb, TX_TABLE_NAME, TX_IMAGES_TABLE_NAME);
    }

    public void run() throws Exception {
        setup();
        twoItemTransaction();
        conflictingTransactions();
        errorHandling();
        badRequest();
        readThenWrite();
        conditionallyCreateOrUpdateWithMapper();
        reading();
        readCommittedWithMapper();
        sweepForStuckAndOldTransactions();
    }

    public void setup() throws Exception {
        print("\n*** setup() ***\n");
        TableHelper tableHelper = new TableHelper(dynamodb);

        // 1. Verify that the transaction table exists, or create it if it doesn't exist
        print("Verifying or creating table " + TX_TABLE_NAME);
        TransactionManager.verifyOrCreateTransactionTable(dynamodb, TX_TABLE_NAME, 1, 1, null);

        // 2. Verify that the transaction item images table exists, or create it otherwise
        print("Verifying or creating table " + TX_IMAGES_TABLE_NAME);
        TransactionManager.verifyOrCreateTransactionImagesTable(dynamodb, TX_IMAGES_TABLE_NAME, 1, 1, null);

        // 3. Create a table to do transactions on
        print("Verifying or creating table " + EXAMPLE_TABLE_NAME);
        List<AttributeDefinition> attributeDefinitions = Arrays.asList(new AttributeDefinition()
                .withAttributeName(EXAMPLE_TABLE_HASH_KEY).withAttributeType(ScalarAttributeType.S));
        List<KeySchemaElement> keySchema = Arrays
                .asList(new KeySchemaElement().withAttributeName(EXAMPLE_TABLE_HASH_KEY).withKeyType(KeyType.HASH));
        ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput().withReadCapacityUnits(1L)
                .withWriteCapacityUnits(1L);

        tableHelper.verifyOrCreateTable(EXAMPLE_TABLE_NAME, attributeDefinitions, keySchema, null,
                provisionedThroughput, null);

        // 4. Wait for tables to be created
        print("Waiting for table to become ACTIVE: " + EXAMPLE_TABLE_NAME);
        tableHelper.waitForTableActive(EXAMPLE_TABLE_NAME, 5 * 60L);
        print("Waiting for table to become ACTIVE: " + TX_TABLE_NAME);
        tableHelper.waitForTableActive(TX_TABLE_NAME, 5 * 60L);
        print("Waiting for table to become ACTIVE: " + TX_IMAGES_TABLE_NAME);
        tableHelper.waitForTableActive(TX_IMAGES_TABLE_NAME, 5 * 60L);
    }

    /**
     * This example writes two items.
     */
    public void twoItemTransaction() {
        print("\n*** twoItemTransaction() ***\n");

        // Create a new transaction from the transaction manager
        Transaction t1 = txManager.newTransaction();

        // Add a new PutItem request to the transaction object (instead of on the AmazonDynamoDB client)
        Map<String, AttributeValue> item1 = new HashMap<String, AttributeValue>();
        item1.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item1"));
        print("Put item: " + item1);
        t1.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item1));
        print("At this point Item1 is in the table, but is not yet committed");

        // Add second PutItem request for a different item to the transaction object
        Map<String, AttributeValue> item2 = new HashMap<String, AttributeValue>();
        item2.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item2"));
        print("Put item: " + item2);
        t1.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item2));
        print("At this point Item2 is in the table, but is not yet committed");

        // Commit the transaction.  
        t1.commit();
        print("Committed transaction.  Item1 and Item2 are now both committed.");

        t1.delete();
        print("Deleted the transaction item.");
    }

    /**
     * This example demonstrates two transactions attempting to write to the same item.  
     * Only one transaction will go through.
     */
    public void conflictingTransactions() {
        print("\n*** conflictingTransactions() ***\n");
        // Start transaction t1
        Transaction t1 = txManager.newTransaction();

        // Construct a primary key of an item that will overlap between two transactions.
        Map<String, AttributeValue> item1Key = new HashMap<String, AttributeValue>();
        item1Key.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("conflictingTransactions_Item1"));
        item1Key = Collections.unmodifiableMap(item1Key);

        // Add a new PutItem request to the transaction object (instead of on the AmazonDynamoDB client)
        // This will eventually get rolled back when t2 tries to work on the same item
        Map<String, AttributeValue> item1T1 = new HashMap<String, AttributeValue>(item1Key);
        item1T1.put("WhichTransaction?", new AttributeValue("t1"));
        print("T1 - Put item: " + item1T1);
        t1.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item1T1));
        print("T1 - At this point Item1 is in the table, but is not yet committed");

        Map<String, AttributeValue> item2T1 = new HashMap<String, AttributeValue>();
        item2T1.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("conflictingTransactions_Item2"));
        print("T1 - Put a second, non-overlapping item: " + item2T1);
        t1.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item2T1));
        print("T1 - At this point Item2 is also in the table, but is not yet committed");

        // Start a new transaction t2
        Transaction t2 = txManager.newTransaction();

        Map<String, AttributeValue> item1T2 = new HashMap<String, AttributeValue>(item1Key);
        item1T1.put("WhichTransaction?", new AttributeValue("t2 - I win!"));
        print("T2 - Put item: " + item1T2);
        t2.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item1T2));
        print("T2 - At this point Item1 from t2 is in the table, but is not yet committed. t1 was rolled back.");

        // To prove that t1 will have been rolled back by this point, attempt to commit it.
        try {
            print("Attempting to commit t1 (this will fail)");
            t1.commit();
            throw new RuntimeException("T1 should have been rolled back. This is a bug.");
        } catch (TransactionRolledBackException e) {
            print("Transaction t1 was rolled back, as expected");
            t1.delete(); // Delete it, no longer needed
        }

        // Now put a second item as a part of t2
        Map<String, AttributeValue> item3T2 = new HashMap<String, AttributeValue>();
        item3T2.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("conflictingTransactions_Item3"));
        print("T2 - Put item: " + item3T2);
        t2.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item3T2));
        print("T2 - At this point Item3 is in the table, but is not yet committed");

        print("Committing and deleting t2");
        t2.commit();

        t2.delete();

        // Now to verify, get the items Item1, Item2, and Item3.
        // More on read operations later. 
        GetItemResult result = txManager.getItem(
                new GetItemRequest().withTableName(EXAMPLE_TABLE_NAME).withKey(item1Key),
                IsolationLevel.UNCOMMITTED);
        print("Notice that t2's write to Item1 won: " + result.getItem());

        result = txManager.getItem(new GetItemRequest().withTableName(EXAMPLE_TABLE_NAME).withKey(item3T2),
                IsolationLevel.UNCOMMITTED);
        print("Notice that t2's write to Item3 also went through: " + result.getItem());

        result = txManager.getItem(new GetItemRequest().withTableName(EXAMPLE_TABLE_NAME).withKey(item2T1),
                IsolationLevel.UNCOMMITTED);
        print("However, t1's write to Item2 did not go through (since Item2 is null): " + result.getItem());
    }

    /**
     * This example shows the kinds of exceptions that you might need to handle
     */
    public void errorHandling() {
        print("\n*** errorHandling() ***\n");

        // Create a new transaction from the transaction manager
        Transaction t1 = txManager.newTransaction();

        boolean success = false;
        try {
            // Add a new PutItem request to the transaction object (instead of on the AmazonDynamoDB client)
            Map<String, AttributeValue> item1 = new HashMap<String, AttributeValue>();
            item1.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item1"));
            print("Put item: " + item1);
            t1.putItem(new PutItemRequest().withTableName(EXAMPLE_TABLE_NAME).withItem(item1));

            // Commit the transaction.  
            t1.commit();
            success = true;
            print("Committed transaction.  We aren't actually expecting failures in this example.");
        } catch (TransactionRolledBackException e) {
            // This gets thrown if the transaction was rolled back by another transaction
            throw e;
        } catch (ItemNotLockedException e) {
            // This gets thrown if there is too much contention with other transactions for the item you're trying to lock
            throw e;
        } catch (DuplicateRequestException e) {
            // This happens if you try to do two write operations on the same item in the same transaction
            throw e;
        } catch (InvalidRequestException e) {
            // This happens if you do something like forget the TableName or key attributes in the request
            throw e;
        } catch (TransactionException e) {
            // All exceptions thrown directly by this library derive from this.  It is a catch-all
            throw e;
        } catch (AmazonServiceException e) {
            // However, your own requests can still fail if they're invalid.  For example, you can get a 
            // ValidationException if you try to add a "number" to a "string" in UpdateItem.  So you have to handle
            // errors from DynamoDB in the same way you did before.  Except now you should roll back the transaction if it fails.
            throw e;
        } finally {
            if (!success) {
                // It can be a good idea to use a "success" flag in this way, to ensure that you roll back if you get any exceptions 
                // from the transaction library, or from DynamoDB, or any from the DynamoDB client library.  These 3 exception base classes are:
                // TransactionException, AmazonServiceException, or AmazonClientExeption.
                // If you forget to roll back, no problem - another transaction will come along and roll yours back eventually.
                try {
                    t1.rollback();
                } catch (TransactionException te) {
                } // ignore, but should probably log
            }

            try {
                t1.delete();
            } catch (TransactionException te) {
            } // ignore, but should probably log
        }
    }

    /**
     * This example shows an example of how to handle errors
     */
    public void badRequest() throws RuntimeException {
        print("\n*** badRequest() ***\n");

        // Create a "success" flag and set it to false.  We'll roll back the transaction in a finally {} if this wasn't set to true by then.
        boolean success = false;
        Transaction t1 = txManager.newTransaction();

        try {
            // Construct a request that we know DynamoDB will reject.
            Map<String, AttributeValue> key = new HashMap<String, AttributeValue>();
            key.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item1"));

            // You cannot "add" a String type attribute.  This request will be rejected by DynamoDB.
            Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
            updates.put("Will this work?", new AttributeValueUpdate().withAction(AttributeAction.ADD)
                    .withValue(new AttributeValue("Nope.")));

            // The transaction library will make the request here, so we actually see
            print("Making invalid request");
            t1.updateItem(new UpdateItemRequest().withTableName(EXAMPLE_TABLE_NAME).withKey(key)
                    .withAttributeUpdates(updates));

            t1.commit();
            success = true;
            throw new RuntimeException("This should NOT have happened (actually should have failed before commit)");
        } catch (AmazonServiceException e) {
            print("Caught a ValidationException. This is what we expected. The transaction will be rolled back: "
                    + e.getMessage());
            // in a real application, you'll probably want to throw an exception to your caller 
        } finally {
            if (!success) {
                print("The transaction didn't work, as expected.  Rolling back.");
                // It can be a good idea to use a "success" flag in this way, to ensure that you roll back if you get any exceptions 
                // from the transaction library, or from DynamoDB, or any from the DynamoDB client library.  These 3 exception base classes are:
                // TransactionException, AmazonServiceException, or AmazonClientExeption.
                // If you forget to roll back, no problem - another transaction will come along and roll yours back eventually.
                try {
                    t1.rollback();
                } catch (TransactionException te) {
                } // ignore, but should probably log
            }

            try {
                t1.delete();
            } catch (TransactionException te) {
            } // ignore, but should probably log
        }
    }

    /**
     * This example shows that reads can be performed in a transaction, and read locks can be upgraded to write locks. 
     */
    public void readThenWrite() {
        print("\n*** readThenWrite() ***\n");

        Transaction t1 = txManager.newTransaction();

        // Perform a GetItem request on the transaction
        print("Reading Item1");
        Map<String, AttributeValue> key1 = new HashMap<String, AttributeValue>();
        key1.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item1"));

        Map<String, AttributeValue> item1 = t1
                .getItem(new GetItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)).getItem();
        print("Item1: " + item1);

        // Now call UpdateItem to add a new attribute.
        // Notice that the library supports ReturnValues in writes
        print("Updating Item1");
        Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
        updates.put("Color",
                new AttributeValueUpdate().withAction(AttributeAction.PUT).withValue(new AttributeValue("Green")));

        item1 = t1.updateItem(new UpdateItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)
                .withAttributeUpdates(updates).withReturnValues(ReturnValue.ALL_NEW)).getAttributes();
        print("Item1 is now: " + item1);

        t1.commit();

        t1.delete();
    }

    @DynamoDBTable(tableName = EXAMPLE_TABLE_NAME)
    public static class ExampleItem {

        private String itemId;
        private String value;
        private Long version;

        @DynamoDBHashKey(attributeName = "ItemId")
        public String getItemId() {
            return itemId;
        }

        public void setItemId(String itemId) {
            this.itemId = itemId;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        @DynamoDBVersionAttribute
        public Long getVersion() {
            return version;
        }

        public void setVersion(Long version) {
            this.version = version;
        }

    }

    /**
     * This example shows how to conditionally create or update an item in a transaction.
     */
    public void conditionallyCreateOrUpdateWithMapper() {
        print("\n*** conditionallyCreateOrUpdateWithMapper() ***\n");

        Transaction t1 = txManager.newTransaction();

        print("Reading Item1");
        ExampleItem keyItem = new ExampleItem();
        keyItem.setItemId("Item1");

        // Performs a GetItem request on the transaction
        ExampleItem item = t1.load(keyItem);
        if (item != null) {
            print("Item1: " + item.getValue());
            print("Item1 version: " + item.getVersion());

            print("Updating Item1");
            item.setValue("Magenta");

            // Performs an UpdateItem request after verifying the version is unchanged as of this transaction
            t1.save(item);
            print("Item1 is now: " + item.getValue());
            print("Item1 version is now: " + item.getVersion());
        } else {
            print("Creating Item1");
            item = new ExampleItem();
            item.setItemId(keyItem.getItemId());
            item.setValue("Violet");

            // Performs a CreateItem request after verifying the version attribute is not set as of this transaction
            t1.save(item);

            print("Item1 is now: " + item.getValue());
            print("Item1 version is now: " + item.getVersion());
        }

        t1.commit();
        t1.delete();
    }

    /**
     * Demonstrates the 3 levels of supported read isolation: Uncommitted, Committed, Locked
     */
    public void reading() {
        print("\n*** reading() ***\n");

        // First, create a new transaction and update Item1, but don't commit yet.
        print("Starting a transaction to modify Item1");
        Transaction t1 = txManager.newTransaction();

        Map<String, AttributeValue> key1 = new HashMap<String, AttributeValue>();
        key1.put(EXAMPLE_TABLE_HASH_KEY, new AttributeValue("Item1"));

        // Show the current value before any changes
        print("Getting the current value of Item1 within the transaction.  This is the strongest form of read isolation.");
        print("  However, you can't trust the value you get back until your transaction commits!");
        Map<String, AttributeValue> item1 = t1
                .getItem(new GetItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)).getItem();
        print("Before any changes, Item1 is: " + item1);

        // Show the current value before any changes
        print("Changing the Color of Item1, but not committing yet");
        Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
        updates.put("Color",
                new AttributeValueUpdate().withAction(AttributeAction.PUT).withValue(new AttributeValue("Purple")));

        item1 = t1.updateItem(new UpdateItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)
                .withAttributeUpdates(updates).withReturnValues(ReturnValue.ALL_NEW)).getAttributes();
        print("Item1 is not yet committed, but if committed, will be: " + item1);

        // Perform an Uncommitted read
        print("The weakest (but cheapest) form of read is Uncommitted, where you can get back changes that aren't yet committed");
        print("  And might be rolled back!");
        item1 = txManager.getItem(new GetItemRequest() // Uncommitted reads happen on the transaction manager, not on a transaction.
                .withKey(key1).withTableName(EXAMPLE_TABLE_NAME), IsolationLevel.UNCOMMITTED).getItem(); // Note the isloationLevel
        print("The read, which could return changes that will be rolled back, returned: " + item1);

        // Perform a Committed read
        print("A strong read is Committed.  This means that you are guaranteed to read only committed changes,");
        print("  but not necessarily the *latest* committed change!");
        item1 = txManager.getItem(new GetItemRequest() // Uncommitted reads happen on the transaction manager, not on a transaction.
                .withKey(key1).withTableName(EXAMPLE_TABLE_NAME), IsolationLevel.COMMITTED).getItem(); // Note the isloationLevel
        print("The read, which should return the same value of the original read, returned: " + item1);

        // Now start a new transaction and do a read, write, and read in it
        Transaction t2 = txManager.newTransaction();

        print("Getting Item1, but this time under a new transaction t2");
        item1 = t2.getItem(new GetItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)).getItem();
        print("Before any changes, in t2, Item1 is: " + item1);
        print(" This just rolled back transaction t1! Notice that this is the same value as *before* t1!");

        updates = new HashMap<String, AttributeValueUpdate>();
        updates.put("Color", new AttributeValueUpdate().withAction(AttributeAction.PUT)
                .withValue(new AttributeValue("Magenta")));

        print("Updating item1 again, but now under t2");
        item1 = t2.updateItem(new UpdateItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)
                .withAttributeUpdates(updates).withReturnValues(ReturnValue.ALL_NEW)).getAttributes();
        print("Item1 is not yet committed, but if committed, will now be: " + item1);

        print("Getting Item1, again, under lock in t2.  Notice that the transaction library makes your write during this transaction visible to future reads.");
        item1 = t2.getItem(new GetItemRequest().withKey(key1).withTableName(EXAMPLE_TABLE_NAME)).getItem();
        print("Under transaction t2, Item1 is going to be: " + item1);

        print("Committing t2");
        t2.commit();

        try {
            print("Committing t1 (this will fail because it was rolled back)");
            t1.commit();
            throw new RuntimeException("Should have been rolled back");
        } catch (TransactionRolledBackException e) {
            print("t1 was rolled back as expected.  I hope you didn't act on the GetItem you did under the lock in t1!");
        }
    }

    /**
     * Demonstrates reading with COMMITTED isolation level using the mapper.
     */
    public void readCommittedWithMapper() {
        print("\n*** readCommittedWithMapper() ***\n");

        print("Reading Item1 with IsolationLevel.COMMITTED");
        ExampleItem keyItem = new ExampleItem();
        keyItem.setItemId("Item1");
        ExampleItem item = txManager.load(keyItem, IsolationLevel.COMMITTED);

        print("Committed value of Item1: " + item.getValue());
    }

    public void sweepForStuckAndOldTransactions() {
        print("\n*** sweepForStuckAndOldTransactions() ***\n");

        // The scan should be done in a loop to follow the LastEvaluatedKey, and done with following the best practices for scanning a table.
        // This includes sleeping between pages, using Limit to limit the throughput of each operation to avoid hotspots,
        // and using parallel scan.
        print("Scanning one full page of the transactions table");
        ScanResult result = dynamodb.scan(new ScanRequest().withTableName(TX_TABLE_NAME));

        // Pick some duration where transactions should be rolled back if they were sitting there PENDING.
        // 
        //long rollbackAfterDurationMills = 5 * 60 * 1000; // Must be idle and PENDING for 5 minutes to be rolled back
        //long deleteAfterDurationMillis = 24 * 60 * 60 * 1000; // Must be completed for 24 hours to be deleted
        long rollbackAfterDurationMills = 1;
        long deleteAfterDurationMillis = 1;
        for (Map<String, AttributeValue> txItem : result.getItems()) {
            print("Sweeping transaction " + txItem);
            try {
                if (TransactionManager.isTransactionItem(txItem)) {
                    Transaction t = txManager.resumeTransaction(txItem);
                    t.sweep(rollbackAfterDurationMills, deleteAfterDurationMillis);
                    print("  - Swept transaction (but it might have been skipped)");
                }
            } catch (TransactionException e) {
                // Log and report an error "unsticking" this transaction, but keep going.
                print("  - Error sweeping transaction " + txItem + " " + e);
            }
        }

    }

    private static void print(CharSequence line) {
        System.out.println(line.toString());
    }
}