Details
-
Type:
Bug
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: 3.1.2, 3.1.x, 3.2.1, 3.2.x
-
Fix Version/s: None
-
Component/s: Core: Transactions, Transport: JDBC
-
Environment:
Sun JDK 1.6.0_30
Windows XP SP3 32bit
-
User impact:Very High
-
Configuration:
-
Similar Issues:None
Description
As you should know, a common use case is the bridge pattern :
- read a message from an inbound endpoint (from a JMS queue or/and a database through JDBC).
- writing the message to an outbound endpoint (to a JMS queue or/and a database through JDBC).
<service name="myBridgeService"> <inbound> <jms:inbound-endpoint queue="/queue/inbox"/> </inbound> <outbound> <multicasting-router> <jms:outbound-endpoint queue="/queue/outbox" /> <jdbc:outbound-endpoint queryKey="insertMessageStmt" /> </multicasting-router> </outbound> </service>
What happens then if something goes wrong when writing the result to the outbound endpoint (eg. jdbc endpoint)? If you don't use XA Transaction on inbound and outbound endpoints, the message will not be rollbacked to the inbound endpoint in case of an Exception. So you will have to solve this issue by configuring <xa-transaction> on each endpoint with the appropriate transaction strategy.
<service name="myBridgeService"> <inbound> <jms:inbound-endpoint queue="/queue/inbox"> <xa-transaction action="ALWAYS_BEGIN" /> </jms:inbound-endpoint> </inbound> <outbound> <multicasting-router> <jms:outbound-endpoint queue="/queue/outbox"> <xa-transaction action="ALWAYS_JOIN" /> </jms:outbound-endpoint> <jdbc:outbound-endpoint queryKey="insertMessageStmt"> <xa-transaction action="ALWAYS_JOIN" /> </jdbc:outbound-endpoint> </multicasting-router> </outbound> </service>
But what happens now if you have a component between inbound and outbound enpoints ?
<service name="myDaoService"> <inbound> <jms:inbound-endpoint queue="/queue/inbox"> <xa-transaction action="ALWAYS_BEGIN" /> </jms:inbound-endpoint> </inbound> <component> <spring-object bean="messageDao" /> </component> <outbound> <multicasting-router> <jms:outbound-endpoint queue="/queue/outbox"> <xa-transaction action="ALWAYS_JOIN" /> </jms:outbound-endpoint> <jdbc:outbound-endpoint queryKey="insertMessageStmt"> <xa-transaction action="ALWAYS_JOIN" /> </jdbc:outbound-endpoint> </multicasting-router> </outbound> </service>
In most cases, the Spring object component (eg. messsageDao) will execute complicated business operations (from a currently existing business API) and also use a DataSource to execute several SQL select and update statements before returning a result to the outbound. These JDBC operations will be handled likely by Spring JdbcTemplate or even HibernateTemplate.
As far as I understand, Mule is fully responsible to handle enlistment/delistment and close of XA resources (see org.mule.transaction.XaTransaction). So by default, XA resources used by a Spring object component will not be handled by Mule and will not participate in the XA transaction started from Mule.
For completeness... I've also found a unit test for this usecase in Mule distribution under MULE_HOME/src/mule-3.1.2-src.zip/org/mule/test/integration/transaction/XATransactionsWithSpringDAO.java. When you execute this test, you will have a successful result but the XA resource used by the dao bean is not enlisted nor delisted in/from the transaction started from Mule.
In order to handle resources from Spring object component, resources should be shared between the endpoint (eg. JdbcConnector) and the Spring object component (eg. Spring JdbcTemplate) in a XA transaction. As a matter of fact, Mule registers in a map all XA resources for a specific XA transaction. If a resource is already registered for the same key, it will reuse that one.
... public synchronized void bindResource(Object key, Object resource) throws TransactionException { if (this.resources.containsKey(key)) { throw new IllegalTransactionStateException(CoreMessages.transactionResourceAlreadyListedForKey(key)); } this.resources.put(key, resource); if (key == null) { this.logger.error("Key for bound resource " + resource + " is null"); } if ((resource instanceof MuleXaObject)) { MuleXaObject xaObject = (MuleXaObject)resource; xaObject.enlist(); } else if ((resource instanceof XAResource)) { enlistResource((XAResource)resource); } else { this.logger.error("Bound resource " + resource + " is neither a MuleXaObject nor XAResource"); } } ...As only one XAConnection to the same DataSource can be enlisted in the transaction, you need to share the same XAConnection obtained from a Mule JdbcConnector or a Spring JdbcTemplate. Both objects should then share the same DataSource because Mule uses the DataSource as the key of the map containing the registered XA resources. So Spring JdbcTemplate should be directly instanciated with the DataSource obtained from the JdbcConnector.
Doing so will let Mule handle the XA resource used by a Spring object component and reuse that resource for a JdbcConnector.
Unfortunately, Spring JdbcTemplate will automatically close immediately the XA resource as soon as any of the org.springframework.jdbc.core.JdbcTemplate.execute() methods is ended; through org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(Connection con, DataSource dataSource)
So we need a specific org.mule.transport.jdbc.xa.DataSourceWrapper instanciated from a JdbcConnector and implementing the org.springframework.jdbc.datasource.SmartDataSource interface to let Mule manage the close of the XA Resource on a transaction rollback or commit (instead of Spring JdbcTemplate).
You will find an implementation of this specific class attached.
... public synchronized void bindResource(Object key, Object resource) throws TransactionException { if (this.resources.containsKey(key)) { throw new IllegalTransactionStateException(CoreMessages.transactionResourceAlreadyListedForKey(key)); } this.resources.put(key, resource); if (key == null) { this.logger.error("Key for bound resource " + resource + " is null"); } if ((resource instanceof MuleXaObject)) { MuleXaObject xaObject = (MuleXaObject)resource; xaObject.enlist(); } else if ((resource instanceof XAResource)) { enlistResource((XAResource)resource); } else { this.logger.error("Bound resource " + resource + " is neither a MuleXaObject nor XAResource"); } } ...