3

I am trying to implement a distributed transaction (XA) in JBoss EAP 6.2 application server so that multiple datastores can be accessed in a single coordinated atomic transaction. To be more precise, I'd like my transactional service method to write to a database table and to a message queue in such a way that these two operations are either both committed or both rolled back consistently (all or nothing).

My approach is based on the following:

  • Use Spring JTA Transaction Manager
  • Configure the entity manager to use an XA JDBC datasource, defined in JBoss application server and accessed via a JNDI name
  • Use ActiveMQ XA Connection Factory for messaging

The problem I am facing is that only the database operation is rolled back. The message written to ActiveMQ queue is always commited regardless of the transaction being rolled back or not.

Key elements of my configuration:

<tx:jta-transaction-manager/>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jtaDataSource" ref="xaDataSource" />
    ...
    <property name="jpaProperties">
        <props>
            ...
            <prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</prop>
        </props>
    </property>
</bean>

<jee:jndi-lookup id="xaDataSource" jndi-name="xaDataSource"/>

<bean id="xaConnectionFactory" class="org.apache.activemq.ActiveMQXAConnectionFactory">
    <property name="brokerURL"> 
        <value>tcp://localhost:61616</value> 
    </property> 
</bean>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="xaConnectionFactory" />
    <property name="defaultDestinationName" value="TEST_QUEUE" />
    <property name="sessionTransacted" value="true"/>
</bean>
Taoufik Mohdit
  • 1,910
  • 3
  • 26
  • 39

1 Answers1

2

I finally got this working. The key was to configure the JMS connection factory within a JBoss Resource Adapter. Detailed steps below:

1. Install Apache ActiveMQ

Version used: 5.11.3

Detailed install instructions can be found here.

Once ActiveMQ installed, Create a queue named TEST_QUEUE (use admin console: http://127.0.0.1:8161/admin/index.jsp)

2. Set up Spring application context

Key elements:

  • Use Spring JTA Transaction Manager tag: this will prompt the use of the app server transaction manager;
  • Configure the datasource bean to use the XA datasource defined in the app server (see XA JDBC datasource setup);
  • Wire the jtaDataSource attribute of the Entity Manager Factory to the XA datasource;
  • Set Hibernate property manager_lookup_class to JBossTransactionManagerLookup;
  • Configure the connection factory bean to use the XA connection factory bean defined in the app server (see XA Connection Factory setup);
  • Set property transactedSession of connection factory bean to false.

e

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:jms="http://www.springframework.org/schema/jms"   xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

<jpa:repositories base-package="com.company.app.repository" />

<context:component-scan base-package="com.company.app" />

<tx:annotation-driven transaction-manager="transactionManager"/>

<tx:jta-transaction-manager/>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jtaDataSource" ref="xaDataSource" />
    <property name="packagesToScan" value="com.company.app.domain" />
    <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</prop>
        </props>
    </property>
</bean>

<jee:jndi-lookup id="xaDataSource" jndi-name="jdbc/xaDataSource"/>

<bean id="xaConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="activemq/ConnectionFactory" />
</bean>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="xaConnectionFactory" />
    <property name="defaultDestinationName" value="TEST_QUEUE" />
    <property name="sessionTransacted" value="false"/>
</bean>

3. Set up application server (JBoss)

For a distributed transaction to work, all the datasources involved must be of type XA. JBoss supports JDBC XA datasource (xa-datasource tag) out of the box. The XA configuration for JMS datasource is achieved by defining appropriate Resource Adapter.

3.1. XA JDBC datasource

In standalone.xml under <subsystem xmlns="urn:jboss:domain:datasources:1.1"> <datasources> add an XA JDBC datasource:

<xa-datasource jndi-name="java:/jdbc/xaDataSource" pool-name="jdbc/xaDataSource" enabled="true">
    <xa-datasource-property name="URL">
        jdbc:oracle:thin:@<hostname>:<port_number>/<SID>
    </xa-datasource-property>
    <xa-datasource-class>oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>
    <driver>ojdbc6-11.2.0.3.jar</driver>
    <security>
        <user-name>db_user</user-name>
        <password>password</password>
    </security>
</xa-datasource>

3.2. XA Conncection Factory

Resource adapters are a concept from the J2EE Connector Architecture (JCA) and are used to interface with Enterprise Information Systems, i.e. systems external to the application server (e.g., relational databases, mainframes, Message-Oriented Middleware, accounting systems, etc.).

First you need to install ActiveMQ RAR (Resource adapter ARchive) in JBoss, by dropping the appropriate RAR file from maven central under \standalone\deployments. Then, in standalone.xml, under <subsystem xmlns="urn:jboss:domain:resource-adapters:1.1"> add the following:

<resource-adapters>
    <resource-adapter id="activemq-rar.rar">
        <archive>
            activemq-rar-5.11.3.rar
        </archive>
        <transaction-support>XATransaction</transaction-support>
        <config-property name="Password">
            admin
        </config-property>
        <config-property name="UserName">
            admin
        </config-property>
        <config-property name="ServerUrl">
            tcp://localhost:61616?jms.rmIdFromConnectionId=true
        </config-property>
        <connection-definitions>
            <connection-definition class-name="org.apache.activemq.ra.ActiveMQManagedConnectionFactory" jndi-name="java:/activemq/ConnectionFactory" enabled="true" pool-name="ConnectionFactory">
                <xa-pool>
                    <min-pool-size>1</min-pool-size>
                    <max-pool-size>20</max-pool-size>
                    <prefill>false</prefill>
                    <is-same-rm-override>false</is-same-rm-override>
                </xa-pool>
            </connection-definition>
        </connection-definitions>
    </resource-adapter>
</resource-adapters>

For further details about installing ActiveMQ RAR in JBoss, refer to RedHat documentation.

4. Make your Service method Transactional

@Service
public class TwoPhaseCommitService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private JmsTemplate jmsTemplate;

    @Transactional
    public void writeToDbAndQueue() {

        final Employee employee = new Employee();

        employee.setFirstName("John");
        employee.setLastName("Smith");

        // persist entity to database
        employeeRepository.save(employee);

        // write message to TEST_QUEUE
        jmsTemplate.send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                return session.createTextMessage(employee.getFirstName());
            }
        });

        // To test rollback uncomment code below:
        // throw new RuntimeException("something went wrong. Transaction must be rolled back!!!");
    }
}
Taoufik Mohdit
  • 1,910
  • 3
  • 26
  • 39