Transaction Propagation with JBoss

Note: accompanying this article is example code located in the narayana git repo at github. To access the code use git to clone the narayana quickstart repo and then checkout the branch called jts-wiki-example:

 

git clone https://github.com/jbosstm/quickstart

cd quickstart

git checkout jts-wiki-example

cd wiki-example

 

JBossAS is a JEE compliant server and as such supports transactional EJB calls via declarative transactions. The server is then responsible for terminating, joining or transparently propagating a transaction to other EJBs. In this way, every EJB participating in the transaction will see a consistent outcome.


This short tech note shows how to configure the system for the use case where the client wishes to start and end the transaction. There are a number of relevant scenarios:


  1. the client invokes two different EJBs in the same JBoss server;
  2. the client invokes an EJB in one JBoss server which then invokes an EJB in a different JBoss server;
  3. the client invokes an EJB in two different JBoss servers (or JEE servers from different vendors);
  4. the client invokes an EJB in a JBoss server and also performs some local work;


Cases 1 and 2 are mandated by the JEE specification whereas case 3 is optional. Case 4 is outside the scope of JEE.


JBoss supports the first 2 cases via "ClientUserTransactionService" which is described in the AS user guide (see http://docs.jboss.org/jbossas/jboss4guide/r5/html/ch4.chapt.html):

The ClientUserTransactionService MBean publishes a
UserTransaction implementation under the JNDIUserTransaction. When the
UserTransaction is obtained with a JNDI lookup from a external client,
a very simple UserTransaction suitable for thin clients is returned.
This UserTransaction implementation only controls the transactions on
the server the UserTransaction object was obtained from. Local
transactional work done in the client is not done within the
transactions started by this UserTransaction object. name

Cases 3 and 4 are only supported if the JTS version of the JBossTS transaction manager is used.

Local Transactions

Cases 1 and 2 are quite straight forward. First obtain a user transaction from the JBoss server hosting the target EJB via JNDI:

Properties properties = new Properties();

// contact a JNDI service running on HOST

String url = "HOST:1099/NameService";

// use the JBoss naming context factory


properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.NamingContextFactory");
properties.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client:org.jnp.interfaces");
properties.setProperty(Context.PROVIDER_URL, url);


Context context = new InitialContext(properties);
// obtain a UserTransaction
UserTransaction userTx = (UserTransaction) context.lookup("UserTransaction");

Now the client is in possession of the all important user transaction object, userTx, which effectively is a transaction proxy managed by the JBoss server running on host HOST. With this proxy the client can start and stop transactions, but there are restrictions on what kind of work can be performed within that transaction. For example invoking EJB methods marked with a transaction attribute of REQUIRED, SUPPORTS or MANDATORY will be transactional (provided the EJB and userTx were obtained from the same server). On the other hand doing work on local resources such as interacting with a database will not become part of the transaction.


Suppose the client wishes to invoke methods on two EJBs obtained from the same server from which he obtained the UserTransaction object. After the client has started a transaction all XA resources updated by methods marked REQUIRED, SUPPORTS or MANDATORY in both EJBs will now be involved in the transaction. When the userTx.commit() method is called all resources will be committed or rolled back together. Any work done on local resources or EJBs obtained from other servers will be not take part in the completion of the transaction.


Attached to this article is a tar archive containing an EJB and a client which demonstrate the behaviour outlined above. The example shows:

  • how to obtain the transaction;
  • how to lookup and invoke the EJB
  • how one EBJ can lookup another EJB and make calls within the same transaction started by the client.

The example also includes a dummy XA resource so you can verify that the two phase commit protocol is being used and experiment with various failure scenarios.

Distributed Transactions

Ok so that was all straight forward but what about cases 3 and 4 where the client wishes to enlist other servers or local resources with the transaction. Since case 4 is a generalization of case 3 we only cover the scenario where the client invokes EJBs on different servers (case 4 is covered at length in the JTS programmers guide).


Scenario 3 is an optional requirement of the JEE specification and is supported by the JBoss application server but requires a distributed transaction manager (TM). JBossTS is the default TM supported by the AS and comes in two flavors: JTA only (which is the default TM shipped with the AS); and a JTS version. JTS is the Java binding to the CORBA Object Transaction Service (OTS). Since the JTS includes the JTA developers of EJBs usually don't need to know about JTS provided they stick to the JTA interfaces (packages prefixed with javax.transaction). The only difference is in how the clients and EJBs are configured. Instructions on how to install JTS into the JBoss server are described elsewhere.


To begin a distributed transaction the client must use OTS interfaces. The JTS version of the JBossTM product provides a simple wrapper around the OTS called OTSManager. OTSManager.get_current() returns a transaction object on which begin/commit etc can be called. Since the OTS is CORBA based the client must initialize an ORB before using any CORBA services. Again, JBossTM provides some simple wrappers for writing programs that use CORBA:

ORB orb = com.arjuna.orbportability.ORB.getInstance("ClientSide");

RootOA oa = com.arjuna.orbportability.OA.getRootOA(orb);

orb.initORB(new String[] {}, null);

oa.initOA();


ORBManager.setORB(orb);
ORBManager.setPOA(oa);

With the ORB initialized, a call to OTSManager.get_current().begin() should successfully start a global transaction.


Normally when an EJB is invoked the RMI calls are carried over the JRMP transport. But with OTS the transaction context needs to be propagated along with the request. RMI can be configured to run over IIOP (an interoperable CORBA transport protocol) which is the standard protocol for propagating an OTS context. Fortunately it is easy to configure JBoss EJBs to use IIOP via the jboss.xml EJB deployment descriptor:

<session>
    <ejb-name>OTSEjb2StatelessBean</ejb-name>
    <jndi-name>OTSEjb2StatelessBean</jndi-name>
    <invoker-bindings>
        <invoker>
            <invoker-proxy-binding-name>iiop</invoker-proxy-binding-name>
        </invoker>
    </invoker-bindings>
</session>

The iiop invoker-proxy-binding-name tells the server to use IIOP to transport EJB calls. So, if the EJB has been configured in this way, when the client invokes an EJB the client-side ORB will put the OTS transaction context onto each EJB call. When the request is received by the ORB running in the server, interceptors installed by the JTS version of JBossTM will unwrap the context making it available to the target EJB (so that any work done by the EJB on XA resources will automatically be enlisted with the clients' transaction).


There is one final point to note - in order to retrieve an EJB that supports the IIOP binding the correct JNDI service must be used:

/**

* Use the CORBA name service to do JNDI lookups. When running inside the AS then use the local one. Otherwise the client

* program must provide a URL that resolves to the correct name service. <HOST> is the IP address of the host where the EJB

* has been registered and the port 3528 is the IIOP port officially assigned to JBoss by IANA:


*/


org.omg.CORBA.ORB norb = org.jboss.iiop.naming.ORBInitialContextFactory.getORB();


if (norb != null) {

    properties.put("java.naming.corba.orb", norb);

} else {

     properties.put(Context.PROVIDER_URL, "corbaloc:<HOST>:3528/NameService

}


properties.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.iiop.naming:org.jboss.naming.client:org.jnp.interfaces");
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
properties.put(Context.OBJECT_FACTORIES, "org.jboss.tm.iiop.client.IIOPClientUserTransactionObjectFactory");


Context context = new InitialContext(properties);


// Now we can use context to lookup EJBs that support the IIOP transport


Attached to this article is a source archive containing an EJB and client which demonstrate how to use distributed transactions. The example shows:

  • how to start an ORB
  • how to obtain and start an OTS transaction;
  • how to lookup and invoke one or more EJBs that support IIOP

Running the Examples

To run the example configure the JBoss AS to use the JTS version of the JBossTM product (with JBossAS 5 there is an ant build script in the examples/jts directory of the distribution that does this automatically - I normally just copy the all configuration to jts and change the server name in build.xml to jts. After running the script I then copy jts to jts2 so I can run two instances).

 

Set JBOSS_HOME and start two JBoss servers (I usually just configure a second IP on my desktop and bind the second instance to it), eg:


    $JBOSS_HOME/bin/run.sh -c jts -b <host 1>
    $JBOSS_HOME/bin/run.sh -c jts2 -b <host 2>  -Djboss.messaging.ServerPeerID=2

 

Change build.xml to ensure that the properties jboss.server and jboss.server2 match the names above.
Change the properties host.from and host.to to match the IPs of the two servers (or specify them via the command
line using -Dhost.from=<host 1> -Dhost.to=<host 2>):
        <property name="host.from" value="<host 1>" />
        <property name="host.to" value="<host 2>" />

 

The build also needs access to the JTS jars - the AS5 distribution includes these. If you are running an earier version of the
AS you will need to modify the property JBOSS_HOME environment variable as appropriate.

 

AS6 Update:

I have updated the source code to include more comprehensive testing of the various configurations. The revised code has been tested against AS trunk. Here's a restatement of the README included in the source bundle:

 

To run the example configure the JBoss AS using AS trunk (will be JBoss AS6) to use the JTS version of the JBossTM product (there is an ant build script in the examples/jts directory of the distribution that does this automatically - I normally just copy the all configuration to jts and change the server name in build.xml to jts). After running the script I then copy jts to jts2 so I can run two instances.

 

AS6 (M2) with JBOSSTS_4_9_0_GA

In AS6 JBossTS uses a bean based configuration:

 

Prepare two servers with JBossTS JTS support.
On both servers enable the alwaysPropagateContext property by editing the file <server>/deploy/transaction-jboss-beans.xml :
    <bean name="JTSEnvironmentBean" class="com.arjuna.ats.jts.common.JTSEnvironmentBean">
        <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.jta:name=JTSEnvironmentBean", exposedInterface=com.arjuna.ats.jts.common.JTSEnvironmentBeanMBean.class, registerDirectly=true)</annotation>
        <constructor factoryClass="com.arjuna.ats.jts.common.jtsPropertyManager" factoryMethod="getJTSEnvironmentBean"/>
        <property name="alwaysPropagateContext">true</property>
    </bean>

 

On the second server edit the same file and set the socketProcessIdPort to use any port. Find the bean CoreEnvironmentBean in <server>/deploy/transaction-jboss-beans.xml and replace socketProcessIdPort with:
    <property name="socketProcessIdPort">0</property>

 

 

AS5.1

The attached tar archive has been tested on jboss EAP 5.1 but tests 4 and 4b (EJB3 over JRMP) fail (which need looking into), the remaining tests (1 through to 6b) should work.

 

Unpack your eap 5.1 download and configure it to use JTS (cd docs/examples/transactions; ant jts).

Next create two servers called jts and jts2 (cp -r server/all server/jts; cp -r server/all server/jts2). If you need recovery make sure the node ids of the TM are different (edit server/jts2/conf/jbossts-properties.xml and change the value of <property name="com.arjuna.ats.arjuna.xa.nodeIdentifier" value="2"/> to 2.

Now start the two servers (on different sets of ports and with a different messaging server id):

 

  ./bin/run.sh -c jts

  ./bin/run.sh -c jts2 -Djboss.service.binding.set=ports-01  -Djboss.messaging.ServerPeerID=2

 

Now unpack the attached archive and deploy the example:

 

  cd jts-example

  ant clean deploy

  ant -Dtest.id= -Dhost.from=localhost -Dhost.to=localhost   # run all the examples (or specify a test.id to run a specific test)

Make the following changes:

Set JBOSS_HOME and start two JBoss servers with configuration changes referred to above.

eg:
    $JBOSS_HOME/bin/run.sh -c jts -b <IP 1>
    $JBOSS_HOME/bin/run.sh -c jts2 -b <IP 2>

 

  1. Make sure JBOSS_HOME is set.
  2. Change the ant build.xml script to ensure that the properties jboss.server and jboss.server2 match the names above.
  3. Change the ant build.xml properties host.from and host.to to match the IPs of the two servers (or specify them via the command
         line using -Dhost.from=<host 1> -Dhost.to=<host 2>):
         <property name="host.from" value="<host 1>" />
         <property name="host.to" value="<host 2>" />
  4. deploy the EJBs for the example:
         ant deploy

Run the tests:

 

The odd numbered tests invoke an EJB method marked with the transaction attribute REQUIRED on two different servers.

 

The even numbered tests invoke an EJB method marked with the transaction attribute REQUIRED which in turn invokes an EJB method on the second server that is marked with the transaction attribute MANDATORY.
[Note that these tests fail if the call to the second EJB do not carry a transaction (because MANDATORY requires a transaction)].

 

Tests 2b, 4b and 6b do not start a client side transaction (relying on the server to create the transaction). The remaining tests all start a transaction on the client side.

 

EJB calls exercise both EJB models. The odd numbered tests make calls to an EJB2 on one server and an EJB3 on the other, whereas each even numbered tests are repeated twice:
     - the invoked EJB invokes an EJB2 on the other server;
     - the invoked EJB invokes an EJB3 on the other server.

 

Tests 7 and 8 make EJB3 calls over IIOP.

 

Tests which are known to fail:

 

Tests 7, 8 and 8b and the second half of tests 6 and 6b are known to fail since EJB3 over IIOP is not supported in AS5 an AS6 (use -Diiop=1 on the ant command line to enable these tests)

 

The tests:


    To run all the tests: ant -Dtest.id= -Diiop=1

    To run tests that do not use EJB3 over IIOP: ant -Dtest.id=

    To run an individual test: ant -Dtest.id=<test id>

 

    Client User Transaction Tests (EJB calls over JRMP)

 

    ant -Dtest.id=1     # EJB2 (call EJBs in different servers within the same transaction)
    ant -Dtest.id=2     # EJB2 (call EJB in one server which then propagates the tx to other server)
    ant -Dtest.id=2b    # EJB2 (call EJB in one server which then propagates the tx to other server)
    ant -Dtest.id=3     # EJB3 (call EJBs in different servers within the same transaction)
    ant -Dtest.id=4     # EJB3 (call EJB in one server which then propagates the tx to other server)
    ant -Dtest.id=4b    # EJB3 (call EJB in one server which then propagates the tx to other server)

 

    OTS Transaction Tests (with EJB2 model, calls over IIOP)

 

    ant -Dtest.id=5     # EJB2 (call EJBs in different servers within the same transaction)
    ant -Dtest.id=6     # EJB2 (call EJB in one server which then propagates the tx to other server)
    ant -Dtest.id=6b    # EJB2 (call EJB in one server which then propagates the tx to other server)

 

    OTS Transaction Tests (with EJB3 model, calls over IIOP)

 

    ant -Dtest.id=7 -Diiop=1    # EJB3 (IIOP calls to EJBs running in different servers within the same transaction)
    ant -Dtest.id=8 -Diiop=1    # EJB3 (IIOP call to an EJB which in turn propagates the tx to the other server)
    ant -Dtest.id=8b -Diiop=1   # EJB3 (IIOP call to an EJB which in turn propagates the tx to the other server)