Transactional aware JDBC connections
For working with multiple database connections in transactionaly reliable wayyou need either hacking on transaction handling (totally not recommended, a good knowledge of XA specification is necessary) or use transaction manager to that for you.
The word multiple is important here as if you want to run only single database connection you are fine to use local JDBC transaction (Connection.setAutoCommit(false), https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#setAutoCommit(boolean)). But if you want to manage transactionally multiple JDBC connections (different databases, running at different database accounts...) then you need transaction manager.
If you use a transaction manager for that purpose (expecting running a standalone application) you need to: initialize the transaction manager, begin transaction and enlist each resource with the transaction for transaction manager to know which are those participants that are expected to be finished with ACID guarantees.
If you use Narayana you have another option to use the Narayana JDBC transaction driver (https://github.com/jbosstm/narayana/blob/master/ArjunaJTA/jdbc/classes/com/arjuna/ats/jdbc/TransactionalDriver.java).
JDBC transactional driver makes your life easier as you can configure the driver once and then get a managed connection, wrapped by the transactional functionality and you don't need to care of if anymore.
Managing the transaction enlistment on your own
The first code example shows how to use transaction manager to manage JDBC connection. There is no JDBC transaction driver involved and you manage enlistment manually.There is enlisted only one resource in this code example which does not require transaction manager to be used in fact. The main purpose of using transaction manager is for managing two or more distinct resources. Another reason could be the offered JTA API which can make the code clearer.
// here we get instance of Narayana transaction manager
TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();
// and beginning the global transaction for XAResources could be enlisted into
tm.begin();
// getting DB2 datasource which then provides XAResource
XADataSource dsXA = neworg.h2.jdbcx.JdbcDataSource();
// the xa datasource has to be filled with information to connection happens, using setters
dsXA.set...();
// from XADataSource getting XAConnection and then the XAResource
XAConnection xaConn = dsXA.getXAConnection();
// transaction manager to be provided with the XAResource
tm.getTransaction().enlistResource(xaConn.getXAResource());
// the business logic in database in the transaction happening here
PreparedStatement ps = xaConn.getConnection().prepareStatement("INSERT INTO TEST values (?, ?)");
ps.setInt(1, 1);
ps.setString(2, "Narayana");
// statement executed and transaction is committed or rolled-back depending of the result
try {
ps.executeUpdate();
tm.commit();
} catch (Exception e) {
tm.rollback();
} finally {
xaConn.close(); // omitting try-catch block
}
You can compare approach of using JDBC local transaction not ensuring reliable resources management with use of Narayana with manual enlistment in the Narayana quickstarts.Managing the transaction enlistment with use of the JDBC transactional driver
How is the same task will be with the transaction driver?First we need an XADataSource to be provided to the transactional driver. Next we request connection from the transactional driver (and not directly from the
XADataSource
). As we requested the XADatasource from the driver it has chance to wrap it and it controls the connection. Thus the resource is automatically enlisted to an active transaction. That way you don't need to think of getting XAConnection and XADataSource and enlisting them to the transaction and you don't need to pass the transaction and the connection from method to a method as parameter but you can simply use the transactional driver connection withdrawal.If you want to use the transactional driver in your project you will need to add two dependencies into configuration of your dependency management system. Here is what to use with maven
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>narayana-jta</artifactId>
<version>5.7.2.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jdbc</artifactId>
<version>5.7.2.Final</version>
</dependency>
There are basically three possibilities how to provide XADataSource
to the transactional driver. Let's go through them.XADataSource provided within Narayana JDBC driver properties
First you can provide directly an instance ofXADataSource
. This single instance is used for the further connection distribution. This settings is done with propertyTransactionalDriver.XADataSource. That key is filled with instance of the XADataSource.
You can examine this example in Narayana quickstart DriverProvidedXADataSource (two resources in use) and check the functionality in the test.
// XADataSource initialized to be passed to transactional driver
XADataSource dsXA = new org.h2.jdbcx.JdbcDataSource();
dsXA.set...();
// the datasource is put as property with the special name
Properties connProperties = new Properties();
connProperties.put(TransactionalDriver.XADataSource, dsXA);
// getting connection when the 'url' is 'jdbc:arjuna' prefix which determines
// the Naryana drive to be used
Connection con = DriverManager.getConnection(TransactionalDriver.arjunaDriver, connProperties);
// starting transaction
TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();
tm.begin();
// db business logic (sql insert query) preparation
PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST values (?, ?)");
ps.setInt(1, 41);
ps.setString(2, "Narayana");
// execution, committing/rolling-back
try {
ps.executeUpdate();
tm.commit();
} catch (Exception e) {
tm.rollback();
} finally {
conn.close();
}
XADataSource bound to JNDI
Another possibility is using JNDI to bind theXADataSource
to and provide the JNDI as part of the url to transactional driver.You can examine this example in Narayana quickstart DriverIndirectRecoverable and check the functionality in the test.
// the JNDI name has to start with the Narayana transactional driver prefix,
// for would be determined that we want connection of transactional driver
// the suffix (here 'ds') is used as JNDI name that XADataSource will be bound to
XADataSource dsXA = JdbcDataSource ds = new JdbcDataSource();
dsXA.set...();
// binding xa datasource to JNDI name 'ds'
InitialContext ctx = new IntitialContext();
ctx.bind("ds", dsXA);
// passing the JNDI name 'ds' as part of the connection url that we demand
// the first part is narayana transactional driver prefix
String dsJndi = TransactionalDriver.arjunaDriver + "ds";
Connection conn = DriverManager.getConnection(dsJndi, new Properties());
// get transaction driver and start transaction
TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();
tm.begin();
// data insertion preparation
PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST values (?, ?)");
ps.setInt(1, 42);
ps.setString(2, "Narayana");
// execute, commit or rollback
try {
ps.executeUpdate();
tm.commit();
} catch (Exception e) {
tm.rollback();
} finally {
conn.close();
}
XADataSource connection data provided in properties file
The third option is about construction a construct of dynamic class. Here the part of the url after the
arjunaDriver
prefix depends on implementation of interface com.arjuna.ats.internal.jdbc.DynamicClass
.Currently there is an only one provided
com.arjuna.ats.internal.jdbc.drivers.PropertyFileDynamicClass
.This expects that the jdbc url contains path to the properties file where connection data for XADataSource class is provided. In particular the property file defines a name of class implementing the
XADataSource
interface. This name is written in the key xaDataSourceClassName
. Next you need to provide connection data (jdbc url, username, password) where each of the propertiesis dynamically invoked as a setter on the particular
XADataSource
instance. Which is exact name of the property depends on the database you and the JDBC driver you use.You can examine this example in Narayana quickstart DriverDirectRecoverable and check the functionality in the test.
// jdbc url is defined with path to the properties file
// see the ./ds.properties as 'getConnection' url parameter
Properties props = new Properties();
props.put(TransactionalDriver.dynamicClass, PropertyFileDynamicClass.class.getName());
Connection conn = DriverManager.getConnection(TransactionalDriver.arjunaDriver
+ "./ds.properties", props);
// starting transaction
TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();
tm.begin();
// data insertion preparation
PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST values (?, ?)");
ps.setInt(1, 43);
ps.setString(2, "Narayana");
// execute, commit or rollback
try {
ps.executeUpdate();
tm.commit();
} catch (Exception e) {
tm.rollback();
} finally {
conn.close();
}
and the
./ds.properties
file could look like this. We use H2 jdbc driver and if you check the API of the driver you will find there setters like setURL
, setUser
and setPassword
which are used to fill connection data after XADataSource
is intialized by default constructor.# implementation of XADataSource
xaDataSourceClassName=org.h2.jdbcx.JdbcDataSource
# properties which will be invoked on dynamically created XADataSource as setters.
# For example there will be call
# JdbcDataSource.setURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1")
URL=jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1
User=sa
Password=sa
In summary
Let's put together properties and their usage. The properties used in the transactional driver could be verified in the code of TransactionalDriver.
TransactionalDriver.arjunaDriver
(jdbc:arjuna:
) is a prefix of jdbc url which defines the Narayana transactional driver is in demand. Data after this prefix is used as parameter for later use.
XADataSource
implementation.TransactionalDriver.XADataSource
when used it defines that implementation of XADataSource was provided as parameter and will be used for connection withdrawalTransactionalDriver.dynamicClass
defines name of class implementing interfacecom.arjuna.ats.internal.jdbc.DynamicClass
which is then used for dynamically creating of the XADataSource.TransactionalDriver.userName
andTransactionalDriver.password
you can use if you the particular connection needs to be specified with the values. They will be used in call ofXADataSource.getXAConnection(username, password)
.TransactionalDriver.poolConnections
is default astrue
. The current behavior is simple. There is created a pool for each jdbc url, internal check verifies if the particular connection is in use, if not then it's passed for the usage otherwise it's created a new connection. The pool capacity is defined by propertyTransactionalDriver.maxConnections
. When the connection is closed then it is returned to the pool and marked as 'not under use'. For some more sophisticated pool management some 3rd party pool management project is recommended.
If this property is set tofalse
then a new connection is returned each time is asked for. That way you need to pass this connection over the application.
3 comments:
Hi,
The example show here is explicitly handling the transaction. Do you have any sample that use
annotation @javax.transaction.Transactional and running on a Jetty Servlet Container ?
Hi John,
If I understand your question correctly, you are looking for examples of integrating Narayana JTA with Jetty. Although, as far as I know, we don't have a quickstart that specifically demonstrates Jetty + Narayana JTA, you can start with the "Standalone JTA 1.2" quickstart [1] and the "JTA and CDI integration" blog post [2]. Your goal is definitely feasible, and I can see that others have already gone down this route [3]. Please feel free to reach out to the team through one of our official channels [4], especially Zulip.
[1] - https://github.com/jbosstm/quickstart/tree/main/jta-1_2-standalone
[2] - https://jbossts.blogspot.com/2019/04/jta-and-cdi-integration.html
[3] - https://groups.google.com/g/narayana-users/search?q=jetty
[4] - https://www.narayana.io/community/
Post a Comment