JBoss.orgCommunity Documentation

JBossJTA Development Guide

Development reference guide for the JBossJTA implementation of the JTA API

by Mark Little, Jonathan Halliday, Andrew Dinn, and Kevin Connor
edited by Misty Stanley-Jones

Abstract

Development reference guide for the ArjunaTA implementation of the JTA API


This manual uses several conventions to highlight certain words and phrases and draw attention to specific pieces of information.

In PDF and paper editions, this manual uses typefaces drawn from the Liberation Fonts set. The Liberation Fonts set is also used in HTML editions if the set is installed on your system. If not, alternative but equivalent typefaces are displayed. Note: Red Hat Enterprise Linux 5 and later includes the Liberation Fonts set by default.

Four typographic conventions are used to call attention to specific words and phrases. These conventions, and the circumstances they apply to, are as follows.

Mono-spaced Bold

Used to highlight system input, including shell commands, file names and paths. Also used to highlight keycaps and key combinations. For example:

The above includes a file name, a shell command and a keycap, all presented in mono-spaced bold and all distinguishable thanks to context.

Key combinations can be distinguished from keycaps by the hyphen connecting each part of a key combination. For example:

The first paragraph highlights the particular keycap to press. The second highlights two key combinations (each a set of three keycaps with each set pressed simultaneously).

If source code is discussed, class names, methods, functions, variable names and returned values mentioned within a paragraph will be presented as above, in mono-spaced bold. For example:

Proportional Bold

This denotes words or phrases encountered on a system, including application names; dialog box text; labeled buttons; check-box and radio button labels; menu titles and sub-menu titles. For example:

The above text includes application names; system-wide menu names and items; application-specific menu names; and buttons and text found within a GUI interface, all presented in proportional bold and all distinguishable by context.

Mono-spaced Bold Italic or Proportional Bold Italic

Whether mono-spaced bold or proportional bold, the addition of italics indicates replaceable or variable text. Italics denotes text you do not input literally or displayed text that changes depending on circumstance. For example:

Note the words in bold italics above — username, domain.name, file-system, package, version and release. Each word is a placeholder, either for text you enter when issuing a command or for text displayed by the system.

Aside from standard usage for presenting the title of a work, italics denotes the first use of a new and important term. For example:

The approach JBossJTA takes for incorporating JDBC connections within transactions is to provide transactional JDBC drivers as conduits for all interactions. These drivers intercept all invocations and ensure that they are registered with, and driven by, appropriate transactions. The driver com.arjuna.ats.jdbc.TransactionalDriver handles all JDBC drivers, implementing the java.sql.Driver interface. If the database is not transactional, ACID properties cannot be guaranteed.

Because JBossJTA provides JDBC connectivity via its own JDBC driver, application code can support transactions with relatively small code changes. Typically, the application programmer only needs to start and terminate transactions.

The JBossJTA driver accepts the following properties, all located in class com.arjuna.ats.jdbc.TransactionalDriver.

username

the database username

password

the database password

createDb

creates the database automatically if set to true. Not all JDBC implementations support this.

dynamicClass

specifies a class to instantiate to connect to the database, instead of using JNDI.

JDBC connections are created from appropriate DataSources. Connections which participate in distributed transactions are obtained from XADataSources. When using a JDBC driver, JBossJTA uses the appropriate DataSource whenever a connection to the database is made. It then obtains XAResources and registers them with the transaction via the JTA interfaces. The transaction service uses these XAResources when the transaction terminates in order to drive the database to either commit or roll back the changes made via the JDBC connection.

JBossJTA JDBC support can obtain XADataSources through the Java Naming and Directory Interface (JNDI) or dynamic class instantiation.

A JDBC driver can use arbitrary DataSources without having to know specific details about their implementations, by using JNDI. A specific DataSource or XADataSource can be created and registered with an appropriate JNDI implementation, and the application, or JDBC driver, can later bind to and use it. Since JNDI only allows the application to see the DataSource or XADataSource as an instance of the interface (e.g., javax.sql.XADataSource) rather than as an instance of the implementation class (e.g., com.mydb.myXADataSource), the application is not tied at build-time to only use a specific implementation.

For the TransactionalDriver class to use a JNDI-registered XADataSource, you need to create the XADataSource instance and store it in an appropriate JNDI implementation. Details of how to do this can be found in the JDBC tutorial available at the Java web site.

Example 2.4. Storing a datasource in a JNDI implementation

XADataSource ds = MyXADataSource();

Hashtable env = new Hashtable();
String initialCtx = PropertyManager.getProperty("Context.INITIAL_CONTEXT_FACTORY");
env.put(Context.INITIAL_CONTEXT_FACTORY, initialCtx);
initialContext ctx = new InitialContext(env);
ctx.bind("jdbc/foo", ds);
 

The Context.INITIAL_CONTEXT_FACTORY property is the JNDI way of specifying the type of JNDI implementation to use.

The application must pass an appropriate connection URL to the JDBC driver:



          Properties dbProps = new Properties();
dbProps.setProperty(TransactionalDriver.userName, "user");
dbProps.setProperty(TransactionalDriver.password, "password");
// the driver uses its own JNDI context info, remember to set it up:
jdbcPropertyManager.propertyManager.setProperty(
                                                "Context.INITIAL_CONTEXT_FACTORY", initialCtx);
jdbcPropertyManager.propertyManager.setProperty(
                                                "Context.PROVIDER_URL", myUrl);
TransactionalDriver arjunaJDBCDriver = new TransactionalDriver();
Connection connection = arjunaJDBCDriver.connect("jdbc:arjuna:jdbc/foo", dbProps);

The JNDI URL must be pre-pended with jdbc:arjuna: in order for the TransactionalDriver to recognize that the DataSource must participate within transactions and be driven accordingly.


Once the connection is established, all operations on the connection are monitored by JBossJTA. you do not need to use the transactional connection within transactions. If a transaction is not present when the connection is used, then operations are performed directly on the database.

You can use transaction timeouts to automatically terminate transactions if a connection is not terminated within an appropriate period.

You can use JBossJTA connections within multiple transactions simultaneously. An example would be different threads, with different notions of the current transaction. JBossJTA does connection pooling for each transaction within the JDBC connection. Although multiple threads may use the same instance of the JDBC connection, internally there may be a separate connection for each transaction. With the exception of method close, all operations performed on the connection at the application level are only performed on this transaction-specific connection.

JBossJTA automatically registers the JDBC driver connection with the transaction via an appropriate resource. When the transaction terminates, this resource either commits or rolls back any changes made to the underlying database via appropriate calls on the JDBC driver.

Once created, the driver and any connection can be used in the same way as any other JDBC driver or connection.


Example 3.1. JDBC example

This simplified example assumes that you are using the transactional JDBC driver provided with JBossTS. For details about how to configure and use this driver see the previous Chapter.

public class JDBCTest

{
    public static void main (String[] args)
    {
        /*
         */
        Connection conn = null;
        Connection conn2 = null;
        Statement stmt = null;        // non-tx statement
        Statement stmtx = null;  // will be a tx-statement
        Properties dbProperties = new Properties();
        try
            {
                System.out.println("\nCreating connection to database: "+url);
                /*
                 * Create conn and conn2 so that they are bound to the JBossTS
                 * transactional JDBC driver. The details of how to do this will
                 * depend on your environment, the database you wish to use and
                 * whether or not you want to use the Direct or JNDI approach. See
                 * the appropriate chapter in the JTA Programmers Guide.
                 */
                stmt = conn.createStatement();  // non-tx statement
                try
                    {
                        stmt.executeUpdate("DROP TABLE test_table");
                        stmt.executeUpdate("DROP TABLE test_table2");
                    }
                catch (Exception e)
                    {
                        // assume not in database.
                    }
                try
                    {
                        stmt.executeUpdate("CREATE TABLE test_table (a INTEGER,b INTEGER)");
                        stmt.executeUpdate("CREATE TABLE test_table2 (a INTEGER,b INTEGER)");
                    }
                catch (Exception e)
                    {
                    }
                try
                    {
                        System.out.println("Starting top-level transaction.");
                        com.arjuna.ats.jta.UserTransaction.userTransaction().begin();
                        stmtx = conn.createStatement(); // will be a tx-statement
                        System.out.println("\nAdding entries to table 1.");
                        stmtx.executeUpdate("INSERT INTO test_table (a, b) VALUES (1,2)");
                        ResultSet res1 = null;
                        System.out.println("\nInspecting table 1.");
                        res1 = stmtx.executeQuery("SELECT * FROM test_table");
                        while (res1.next())
                            {
                                System.out.println("Column 1: "+res1.getInt(1));
                                System.out.println("Column 2: "+res1.getInt(2));
                            }
                        System.out.println("\nAdding entries to table 2.");
                        stmtx.executeUpdate("INSERT INTO test_table2 (a, b) VALUES (3,4)");
                        res1 = stmtx.executeQuery("SELECT * FROM test_table2");
                        System.out.println("\nInspecting table 2.");
                        while (res1.next())
                            {
                                System.out.println("Column 1: "+res1.getInt(1));
                                System.out.println("Column 2: "+res1.getInt(2));
                            }
                        System.out.print("\nNow attempting to rollback changes.");
                        com.arjuna.ats.jta.UserTransaction.userTransaction().rollback();
                        com.arjuna.ats.jta.UserTransaction.userTransaction().begin();
                        stmtx = conn.createStatement();
                        ResultSet res2 = null;
                        System.out.println("\nNow checking state of table 1.");
                        res2 = stmtx.executeQuery("SELECT * FROM test_table");
                        while (res2.next())
                            {
                                System.out.println("Column 1: "+res2.getInt(1));
                                System.out.println("Column 2: "+res2.getInt(2));
                            }
                        System.out.println("\nNow checking state of table 2.");
                        stmtx = conn.createStatement();
                        res2 = stmtx.executeQuery("SELECT * FROM test_table2");
                        while (res2.next())
                            {
                                System.out.println("Column 1: "+res2.getInt(1));
                                System.out.println("Column 2: "+res2.getInt(2));
                            }
                        com.arjuna.ats.jta.UserTransaction.userTransaction().commit(true);
                    }
                catch (Exception ex)
                    {
                        ex.printStackTrace();
                        System.exit(0);
                    }
            }
        catch (Exception sysEx)
            {
                sysEx.printStackTrace();
                System.exit(0);
            }
    }

This class implements the XAResourceRecovery interface for XAResources. The parameter supplied in setParameters can contain arbitrary information necessary to initialize the class once created. In this example, it contains the name of the property file in which the database connection information is specified, as well as the number of connections that this file contains information on. Each item is separated by a semicolon.

This is only a small example of the sorts of things an XAResourceRecovery implementer could do. This implementation uses a property file that is assumed to contain sufficient information to recreate connections used during the normal run of an application so that recovery can be performed on them. Typically, user-names and passwords should never be presented in raw text on a production system.


Some error-handling code is missing from this example, to make it more readable.

Example 3.3. Failure recovery example with BasicXARecovery

/*

 * Some XAResourceRecovery implementations will do their startup work here,
 * and then do little or nothing in setDetails. Since this one needs to know
 * dynamic class name, the constructor does nothing.
 */
public BasicXARecovery () throws SQLException
{
    numberOfConnections = 1;
    connectionIndex = 0;
    props = null;
}
/**
 * The recovery module will have chopped off this class name already. The
 * parameter should specify a property file from which the url, user name,
 * password, etc. can be read.
 * 
 * @message com.arjuna.ats.internal.jdbc.recovery.initexp An exception
 *          occurred during initialisation.
 */
public boolean initialise (String parameter) throws SQLException
{
    if (parameter == null) 
        return true;
    int breakPosition = parameter.indexOf(BREAKCHARACTER);
    String fileName = parameter;
    if (breakPosition != -1)
        {
            fileName = parameter.substring(0, breakPosition - 1);
            try
                {
                    numberOfConnections = Integer.parseInt(parameter
                                                           .substring(breakPosition + 1));
                }
            catch (NumberFormatException e)
                {
                    return false;
                }
        }
    try
        {
            String uri = com.arjuna.common.util.FileLocator
                .locateFile(fileName);
            jdbcPropertyManager.propertyManager.load(XMLFilePlugin.class
                                                     .getName(), uri);
            props = jdbcPropertyManager.propertyManager.getProperties();
        }
    catch (Exception e)
        {
            return false;
        }
    return true;
}
/**
 * @message com.arjuna.ats.internal.jdbc.recovery.xarec {0} could not find
 *          information for connection!
 */
public synchronized XAResource getXAResource () throws SQLException
{
    JDBC2RecoveryConnection conn = null;
    if (hasMoreResources())
        {
            connectionIndex++;
            conn = getStandardConnection();
            if (conn == null) conn = getJNDIConnection();
        }
    return conn.recoveryConnection().getConnection().getXAResource();
}
public synchronized boolean hasMoreResources ()
{
    if (connectionIndex == numberOfConnections) 
        return false;
    else
        return true;
}
private final JDBC2RecoveryConnection getStandardConnection ()
    throws SQLException
{
    String number = new String("" + connectionIndex);
    String url = new String(dbTag + number + urlTag);
    String password = new String(dbTag + number + passwordTag);
    String user = new String(dbTag + number + userTag);
    String dynamicClass = new String(dbTag + number + dynamicClassTag);
    Properties dbProperties = new Properties();
    String theUser = props.getProperty(user);
    String thePassword = props.getProperty(password);
    if (theUser != null)
        {
            dbProperties.put(TransactionalDriver.userName, theUser);
            dbProperties.put(TransactionalDriver.password, thePassword);
            String dc = props.getProperty(dynamicClass);
            if (dc != null)
                dbProperties.put(TransactionalDriver.dynamicClass, dc);
            return new JDBC2RecoveryConnection(url, dbProperties);
        }
    else
        return null;
}
private final JDBC2RecoveryConnection getJNDIConnection ()
    throws SQLException
{
    String number = new String("" + connectionIndex);
    String url = new String(dbTag + jndiTag + number + urlTag);
    String password = new String(dbTag + jndiTag + number + passwordTag);
    String user = new String(dbTag + jndiTag + number + userTag);
    Properties dbProperties = new Properties();
    String theUser = props.getProperty(user);
    String thePassword = props.getProperty(password);
    if (theUser != null)
        {
            dbProperties.put(TransactionalDriver.userName, theUser);
            dbProperties.put(TransactionalDriver.password, thePassword);
            return new JDBC2RecoveryConnection(url, dbProperties);
        }
    else
        return null;
}
private int numberOfConnections;
private int connectionIndex;
private Properties props;
private static final String dbTag = "DB_";
private static final String urlTag = "_DatabaseURL";
private static final String passwordTag = "_DatabasePassword";
private static final String userTag = "_DatabaseUser";
private static final String dynamicClassTag = "_DatabaseDynamicClass";
private static final String jndiTag = "JNDI_";
/*
 * Example:
 * 
 * DB2_DatabaseURL=jdbc\:arjuna\:sequelink\://qa02\:20001
 * DB2_DatabaseUser=tester2 DB2_DatabasePassword=tester
 * DB2_DatabaseDynamicClass=com.arjuna.ats.internal.jdbc.drivers.sequelink_5_1
 * 
 * DB_JNDI_DatabaseURL=jdbc\:arjuna\:jndi DB_JNDI_DatabaseUser=tester1
 * DB_JNDI_DatabasePassword=tester DB_JNDI_DatabaseName=empay
 * DB_JNDI_Host=qa02 DB_JNDI_Port=20000
 */
private static final char BREAKCHARACTER = ';'; // delimiter for parameters

You can use the class com.arjuna.ats.internal.jdbc.recovery.JDBC2RecoveryConnection to create a new connection to the database using the same parameters used to create the initial connection.


JBoss Application Server is discussed here. Refer to the documentation for your application server for differences.

Revision History
Revision 0Thu Oct 28 2010Misty Stanley-Jones
Initial conversion of book into Docbook
Revision 1Thu Apr 14 2011Tom Jenkinson
Moved some content to the main development guide