ArjunaJTS supports the construction of both local and distributed transactional
applications which access databases using the JDBC 2.0 APIs. JDBC 2.0 supports
two-phase commit of transactions, and is similar to the XA X/Open standard.
The JDBC 2.0 support is found in the com.arjuna.ats.jdbc package.
Transactional Driver
The ArjunaJTS approach to incorporating JDBC connections within transactions
is to provide transactional JDBC drivers through which all interactions occur.
These drivers intercept all invocations and ensure that they are registered
with, and driven by, appropriate transactions. There is a single type of transactional
driver through which any JDBC driver can be driven; obviously if the database
is not transactional then ACID properties cannot be guaranteed. This driver
is com.arjuna.ats.jdbc.TransactionalDriver, which implements the java.sql.Driver
interface.
The driver may be directly instantiated and used within an application. For
example:
TransactionalDriver arjunaJDBC2Driver = new TransactionalDriver();
It can be registered with the JDBC driver manager (java.sql.DriverManager)
by adding them to the Java system properties. The jdbc.drivers property contains
a list of driver class names, separated by colons, that are loaded by the JDBC
driver manager when it is initialised, for instance:
jdbc.drivers=foo.bar.Driver:mydata.sql.Driver:bar.test.myDriver
On running an application, it is the DriverManager's responsibility to load
all the drivers found in the system property jdbc.drivers. For example, this
is where the driver for the Oracle database may be defined. When opening a connection
to a database it is the DriverManager' s role to choose the most appropriate
driver from the previously loaded drivers.
A program can also explicitly load JDBC drivers at any time. For example,
the my.sql.Driver is loaded with the following statement:
Class.forName("my.sql.Driver");
Calling Class.forName() will automatically register the driver with the JDBC
driver manager. It is also possible to explicitly create an instance of the
JDBC driver using the registerDriver method of the DriverManager. This is the
case for instance for the TransactionalDriver that can be registered as follow:
TransactionalDriver arjunaJDBC2Driver = new TransactionalDriver();
DriverManager.registerDriver(arjunaJDBC2Driver);
When you have loaded a driver, it is available for making a connection with
a DBMS.
Making Connections
Once a driver is loaded and ready for a connection to be made, instances of
a Connection class can be created using the getConnection method on the DriverManager,
as follow:
Connection con = DriverManager.getConnection(url, username, password);
From its version 2.0, the JDBC API has introduced a new way to obtain instances
of the Connection class. This is the case of the interfaces DataSource and XADataSource
that creates transactional connections. When using a JDBC 2.0 driver, ArjunaTA
will use the appropriate DataSource whenever a connection to the database is
made. It will then obtain XAResources and register them with the transaction
via the JTA interfaces. It is these XAResources which the transaction service
will use when the transaction terminates in order to drive the database to either
commit or rollback the changes made via the JDBC connection.
There are two ways in which the ArjunaTA JDBC 2.0 support can obtain XADataSources.
These will be explained in the following sections. Note, for simplicity we shall
assume that the JDBC 2.0 driver is instantiated directly by the application.
-
Java Naming and Directory Interface (JNDI)
To get the ArjunaJDBC2Driver class to use a JNDI registered XADataSource
it is first necessary 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 2.0 tutorial available at JavaSoft. An example is show below:
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);
Where the Context.INITIAL_CONTEXT_FACTORY property is the JNDI way of
specifying the type of JNDI implementation to use.
Then the application must pass an appropriate connection URL to the JDBC
2.0 driver:
Properties dbProps = new Properties();
dbProps.setProperty(TransactionalDriver.userName, "user");
dbProps.setProperty(TransactionalDriver.password, "password");
TransactionalDriver arjunaJDBC2Driver = new TransactionalDriver();
Connection connection = arjunaJDBC2Driver.
connect("jdbc:arjuna:jdbc/foo", dbProps);
The JNDI URL must be pre-pended with jdbc:arjuna:
in order for the ArjunaJDBC2Driver to recognise that the DataSource must
participate within transactions and be driven accordingly.
-
Dynamic class instantiation
Many JDBC 2.0 implementations provide proprietary implementations of XADataSources
that provide non-standard extensions to the specification. In order to allow
the application to remain isolated from the actual JDBC 2.0 implementation
it is using and yet continue to be able to use these extensions, ArjunaTA
hides the details of these proprietary implementations using dynamic class
instantiation. In addition, the use of JNDI is not required when using this
mechanism because the actual implementation of the XADataSource will be
directly instantiated, albeit in a manner which will not tie an application
or driver to a specific implementation. ArjunaTA therefore has several classes
which are for specific JDBC 2.0 implementations, and these can be selected
at runtime by the application setting the dynamicClass property appropriately:
Database Type
|
Property Name
|
Cloudscape 3.6 |
com.arjuna.ats.internal.jdbc.drivers.cloudscape_3_6 |
Sequelink 5.1 |
com.arjuna.ats.internal.jdbc.drivers.sequelink_5_1 |
Oracle 8.1.6 |
com.arjuna.ats.internal.jdbc.drivers.oracle_8_1_6 |
SQL Server 2000 |
com.arjuna.ats.internal.jdbc.drivers.sqlserver_2_2 |
The application code must specify which dynamic class the TransactionalDriver
should instantiate when setting up the connection:
Properties dbProps = new Properties();
dbProps.setProperty(TransactionalDriver.userName, "user");
dbProps.setProperty(TransactionalDriver.password, "password");
dbProps.setProperty(TransactionalDriver.dynamicClass,
"com.arjuna.ats.internal.jdbc.drivers.sequelink_5_0");
TransactionalDriver arjunaJDBC2Driver = new TransactionalDriver();
Connection connection = arjunaJDBC2Driver.connect("jdbc:arjuna:
sequelink://host:port;databaseName=foo",dbProperties);
Note on properties used by the com.arjuna.ats.jdbc.TransactionalDriver class
-
userName: the user name to use when attempting to connect to the
database.
-
password: the password to use when attempting to connect to the
database.
-
createDb: if set to true, the driver will attempt to create the
database when it connects. This may not be supported by all JDBC 2.0 implementations.
-
dynamicClass: this specifies a class to instantiate to connect
to the database, rather than using JNDI.
Using the Connection
Once the connection has been established (for example, using the java.sql.DriverManager.getConnection
method), all operations on the connection will be monitored by ArjunaTA. Once
created, the driver and any connection can be used in the same way as any other
JDBC driver or connection.
ArjunaTA connections can be used within multiple different transactions simultaneously,
i.e., different threads, with different notions of the current transaction,
may use the same JDBC connection. ArjunaTA does connection pooling for each
transaction within the JDBC connection. So, although multiple threads may use
the same instance of the JDBC connection, internally this may be using a different
connection instance per transaction. With the exception of close, all operations
performed on the connection at the application level will only be performed
on this transaction-specific connection.
ArjunaTA will automatically register the JDBC driver connection with the transaction
via an appropriate resource . When the transaction terminates, this resource
will be responsible for either committing or rolling back any changes made to
the underlying database via appropriate calls on the JDBC driver.
Further reading
More details on the way to manage applications using the JDBC API can be found
in the ArjunaTA Programming Guide.