I'm trying to manage JMS transaction with Spring and HornetQ.
This is the code I wrote:
public void receive() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
Message msg = jmsTemplate.receive(queue);
boolean success = false;
if (msg != null) {
try {
success = handleMessage(msg);
if (success) {
msg.acknowledge(); // session is still open within the transaction
}
} catch (JMSException e) {
transactionManager.rollback(status);
}
if (success)
transactionManager.commit(status);
else
transactionManager.rollback(status):
}
}
I'm doing a synchronous read from the queue, with timeout set to 0, since I don't wanna block on the read. Because of this I have to check if something was actually received.
This is an excerpt of my applicationContext.xml:
<bean id="inVMConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:/ConnectionFactory</value>
</property>
</bean>
<bean id="cachedConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="inVMConnectionFactory" />
</bean>
<bean id="producer" class="it.ubiquity.gestoreprofilazione.onweb.OnWebProducer" scope="singleton">
<property name="queue" ref="retryQueue" />
<property name="connectionFactory" ref="cachedConnectionFactory" />
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="cachedConnectionFactory" />
<property name="sessionTransacted" value="true" />
<property name="sessionAcknowledgeMode" value="#{T(javax.jms.Session).CLIENT_ACKNOWLEDGE}" />
<property name="pubSubDomain" value="false" />
<property name="receiveTimeout" value="# {T(org.springframework.jms.core.JmsTemplate).RECEIVE_TIMEOUT_NO_WAIT}" />
</bean>
<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="cachedConnectionFactory" />
</bean>
<bean id="consumer" class="it.ubiquity.gestoreprofilazione.onweb.OnWebConsumer" scope="singleton">
<property name="queue" ref="retryQueue" />
<property name="jmsTemplate" ref="jmsTemplate" />
<property name="transactionManager" ref="jmsTransactionManager" />
</bean>
The problem I have is quite strange: the first time I receive a message, handleMessage fail, so I rollback the transaction. Then nothing more happens. If I check with the JMX console, I can see there's one message on the queue. Now, if I restart JBoss, the messages is receive again and again, as expected.
Maybe there's something wrong with my configuration, but why it works after a reboot?
HornetQ 2.2.10
JBoss 5.1.0
Spring 3.1.2
UPDATE
With debugging enabled I see the first time:
DEBUG [org.springframework.jms.connection.JmsTransactionManager]
(baseScheduler-1) Creating new transaction with name [null]:
PROPAGATION_REQUIRED,ISOLATION_DEFAULT
and after the first rollback, on subsequent receive and rollback I see:
DEBUG [org.springframework.jms.connection.JmsTransactionManager]
(baseScheduler-1) Participating in existing transaction
Instead, after restarting JBoss, I read that the transaction is actually rolled back:
DEBUG [org.springframework.jms.connection.JmsTransactionManager]
(baseScheduler-1) Initiating transaction rollback 2012-11-05
09:54:14,436 DEBUG
[org.springframework.jms.connection.JmsTransactionManager]
(baseScheduler-1) Rolling back JMS transaction on Session
So, why the rollback it's not happening the first time, and as soon I restart the server it happens all the time? What am I doing wrong?
Ok, finally I've managed to understand my mistake :)
In the outer if's else I should have committed the transaction. That's why the mechanism works after restarting JBoss: there is no transaction hanging on.
Related
I am using different connection factories for sending and receiving messages, having trouble with partial commit issues incase of delivey failures. jms:message-driven-channel-adapter uses the receiveConnectionFactory ro receive the messages from the queue. jms:outbound-channel-adapter uses the deliverConnectionFactory to send the messages multiple to downstream queues. We have only one JmsTransactionManager which uses the receiveConnectionFactory and the jms:outbound-channel-adapter configured with session-transacted="true".
<beans>
<bean id="transactionManager"
class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="receiveConnectionFactory" />
</bean>
<bean id="receiveConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory">
<bean class="com.ibm.mq.jms.MQQueueConnectionFactory">
<property name="hostName" value="${mq.host}" />
<property name="channel" value="${mq.channel}" />
<property name="port" value="${mq.port}" />
</bean>
</property>
<property name="sessionCacheSize" value="${receive.factory.cachesize}" />
<property name="cacheProducers" value="${receive.cache.producers.enabled}" />
<property name="cacheConsumers" value="${receive.cache.consumers.enabled}" />
</bean>
<bean id="deliverConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory">
<bean class="com.ibm.mq.jms.MQQueueConnectionFactory">
<property name="hostName" value="${mq.host}" />
<property name="channel" value="${mq.channel}" />
<property name="port" value="${mq.port}" />
</bean>
</property>
<property name="sessionCacheSize" value="${send.factory.cachesize}" />
<property name="cacheProducers" value="${send.cache.producers.enabled}" />
<property name="cacheConsumers" value="${send.cache.consumers.enabled}" />
</bean>
<tx:advice id="txAdviceNew" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="send" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdviceNew" pointcut="bean(inputChannel)" />
<aop:advisor advice-ref="txAdviceNew" pointcut="bean(errorChannel)" />
</aop:config>
<jms:message-driven-channel-adapter
id="mdchanneladapter" channel="inputChannel" task-executor="myTaskExecutor"
connection-factory="receiveConnectionFactory" destination="inputQueue"
error-channel="errorChannel" concurrent-consumers="${num.consumers}"
max-concurrent-consumers="${max.num.consumers}" max-messages-per-task="${max.messagesPerTask}"
transaction-manager="transactionManager" />
<jms:outbound-channel-adapter
connection-factory="deliverConnectionFactory" session-transacted="true"
destination-expression="headers.get('Deliver')" explicit-qos-enabled="true" />
</beans>
When there is MQ exception on any one destination, the partial commit occurs and then the failure queue commit happens. I am looking to see if I am missing some configuration to join the transactions so that the partial commit never happens.
I tried with only one connection factory for both send and receive (receiveConnectionFactory) and the parital commit is not happening, everything works as expected.
I tried with only one connection factory for both send and receive (receiveConnectionFactory) and the parital commit is not happening, everything works as expected.
That's the right way to go in your case.
I see that your two ConnectionFactories are only different by their objects. Everything rest looks like the same target MQ server.
If you definitely can't live with only one ConnectionFactory, you should consider to use JtaTransactionManager or configure org.springframework.data.transaction.ChainedTransactionManager for two JmsTransactionManagers - one per connection factory.
See Dave Syer's article on the matter: https://www.javaworld.com/article/2077963/open-source-tools/distributed-transactions-in-spring--with-and-without-xa.html
I wish to execute few insert queries within a transaction block where if there is any error all the inserts will be rolled back.
I am using MySQL database and Spring TransactionManager for this.
Also the table type is InnoDB
I have done my configuration by following the steps mentioned here.
Following is my code (for now only one query)
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = null;
status = transactionManager.getTransaction(def);
jdbcTemplate.execute(sqlInsertQuery);
transactionManager.rollback(status);
Spring config xml:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Datasource config:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="maxIdle" value="${jdbc.maxIdle}" />
<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
<property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
<property name="testOnReturn" value="${jdbc.testOnReturn}" />
<property name="validationQuery" value="${jdbc.validationQuery}" />
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
<!--<property name="removeAbandoned" value="true"/> <property name="removeAbandonedTimeout"
value="10"/> <property name="logAbandoned" value="false"/> -->
<property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}" />
</bean>
This code works perfectly fine and the record gets inserted.
But the rollback doesnt work! It executes the rollback statement without any error but to no effect.
Can anyone guide me where am I going wrong?
It appears that the issues is your datasource is not set to have autocommit off.
<property name="defaultAutoCommit" value="false"/>
Give that a try. I have never used the TransactionManager outside of a proxy so I am not sure if there any other gotchas using it directly like this, but I would recommend you look at either AOP transactions or the convience AOP proxy annotation #Transactional just because it is more common.
EDIT:
I was finally able to resolve this by doing the following:
dmlDataSource.setDefaultAutoCommit(false); //set autocommit to false explicitly.
Exception ex = (Exception)transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus ts) {
try {
dmlJdbcTemplate.execute(sqlInsertQuery);
ts.setRollbackOnly();
dmlDataSource.setDefaultAutoCommit(true); // set autocommit back to true
return null;
} catch (Exception e) {
ts.setRollbackOnly();
LOGGER.error(e);
dmlDataSource.setDefaultAutoCommit(true); // set autocommit back to true
return e;
}
}
});
I am not using the transaction manager now. Using the trasactionTemplate and doing the following:
Exception ex = (Exception)transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus ts) {
try {
dmlJdbcTemplate.execute(sqlInsertQuery);
ts.setRollbackOnly();
return null;
} catch (Exception e) {
ts.setRollbackOnly();
LOGGER.error(e);
return e;
}
}
});
after using #Moles-JWS's answer I am now able to rollback successfully. But I want to handle this only in this method and not change the global configuration of the datasource.
Can I do it here programmatically?
I have a simple standalone application to test transaction management with Spring.
Have Oracle Express Edition.
Run the following to enable XA
grant select on sys.dba_pending_transactions to user_test;
grant select on sys.pending_trans$ to user_test;
grant select on sys.dba_2pc_pending to user_test;
grant execute on sys.dbms_system to user_test;
My Java code is pretty much as follow:
public class DbUpdater
{
private static final ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"spring_transactions.xml"});
private static Logger log = LoggerFactory.getLogger(DbUpdater.class);
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void updateData() {
IMasterDAO ds1 = context.getBean("masterDao", IMasterDAO.class);
log.info("Insert using ds1");
ds1.insert("insert into users values(?,?)", "user1", "John Hamilton");
log.info("Insert using ds1 finished successfully");
throw new RuntimeException("A runtime exception");
}
}
So all the idea is too see transaction rolling back.
I run with several configuration examples and record is committed all the time.
No rollback is performed. No errors nothing, only expected
Exception in thread "main" java.lang.RuntimeException: A runtime exception
at com.test.spring.transation.DbUpdater.updateData(DbUpdater.java:22)
My last config is this:
<bean id="txManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="bitronixTransactionManager" />
<property name="userTransaction" ref="bitronixTransactionManager" />
</bean>
<bean id="btmConfig" factory-method="getConfiguration"
class="bitronix.tm.TransactionManagerServices">
<property name="serverId" value="spring-btm" />
</bean>
<bean id="bitronixTransactionManager" factory-method="getTransactionManager"
class="bitronix.tm.TransactionManagerServices"
depends-on="btmConfig,dataSource"
destroy-method="shutdown" />
<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource"
init-method="init" destroy-method="close">
<property name="className" value="oracle.jdbc.xa.client.OracleXADataSource"/>
<property name="uniqueName" value="myOracleDataSource"/>
<property name="minPoolSize" value="0"/>
<property name="maxPoolSize" value="5"/>
<property name="allowLocalTransactions" value="true"/>
<property name="testQuery" value="select sysdate from dual"/>
<property name="driverProperties">
<props>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="URL">${jdbc.url}</prop>
</props>
</property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="masterDao" class="com.test.spring.transation.MasterDAO">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
</beans>
The JDBC template being instantiated via supplied data source, seems to work with its own transaction [there by the automatic begin and commit transaction]. The exception is thrown after that which operates in seperate commit/rollback cycle and hence you see that the writes persisted. To verify it, you can move the code to throw exception in MasterDAO class and examine rollback.
I use ActiveMq as JMS server and Atomikos as transaction manager.
On ActiveMq Admin web-interface I see that one message was enqueued, but 2 (!) messages were dequeued.
However, jms consumer process message only once, there is no duplication in processing. When I use simple Spring JmsTransactionManager, there are one enqueued and one dequeued messages. The problem appeares only for Atomikos JTA transaction manager.
What is the problem? How to configure Atomikos not to see twice dequeued messages?
My configuration is below. It almost the same as in tutorial. BTW, Atomikos's spring integration example works well but it uses Spring 2.0 whereas I use Spring 3.1.
<bean id="activeMqXaConnectionFactory" class="org.apache.activemq.spring.ActiveMQXAConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<bean id="atomikosQueueConnectionFactory" class="com.atomikos.jms.QueueConnectionFactoryBean" init-method="init">
<property name="resourceName" value="xaMq"/>
<property name="xaQueueConnectionFactory" ref="activeMqXaConnectionFactory"/>
</bean>
<bean id="singleConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="atomikosQueueConnectionFactory"/>
</bean>
<bean id="jmsDefaultContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="singleConnectionFactory"/>
<property name="messageListener" ref="consumer"/>
<property name="concurrentConsumers" value="1"/>
<property name="destinationName" value="q.jtaxa"/>
<property name="receiveTimeout" value="3000"/>
<property name="recoveryInterval" value="5000"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="sessionTransacted" value="true"/>
</bean>
<!-- Transactions -->
<!--Construct Atomikos UserTransactionManager, needed to configure Spring-->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
destroy-method="close">
<!-- when close is called, should we force transactions to terminate or not? -->
<property name="forceShutdown" value="true"/>
</bean>
<!-- Also use Atomikos UserTransactionImp, needed to configure Spring -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300"/>
</bean>
<!-- Configure the Spring framework to use JTA transactions from Atomikos -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager"/>
<property name="userTransaction" ref="atomikosUserTransaction"/>
<property name="nestedTransactionAllowed" value="true"/>
</bean>
Consumer class is:
#Component
public class Consumer implements MessageListener {
#Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
TextMessage textMsg = (TextMessage) message;
System.out.println(" " + textMsg.getText());
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
}
I found what was wrongly configured. It was "atomikosQueueConnectionFactory". I did accroding to the tutorial, but it should be just AtomikosConnectionFactoryBean class not QueueConnectionFactoryBean.
I deleted atomikosQueueConnectionFactory and added atomikosConnectionFactory
<bean id="atomikosConnectionFactory" class="com.atomikos.jms.AtomikosConnectionFactoryBean" init-method="init">
<property name="uniqueResourceName" value="amq1"/>
<property name="xaConnectionFactory" ref="mqXaConnectionFactory"/>
</bean>
It works fine after it.
I found proper configuration here.
What is the best way to perform initialization on DefaultMessageListenerContainer initialization? Currently I am waiting for first message, and keep track of it using a boolean variable, which isn't so pretty. Is there a better way ? I want to read and load certain data into the cache once the Message Driven POJO is started up, so the message processing is faster.
(Edited)
Spring Config Fragement:
<bean id="itemListener" class="com.test.ItemMDPImpl" autowire="byName" />
<bean id="itemListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="itemListener" />
<property name="defaultListenerMethod" value="processItems" />
<property name="messageConverter" ref="itemMessageConverter" />
</bean>
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="itemMqConnectionFactory" />
<property name="destinationName" value="${item_queue_name}" />
<property name="messageListener" ref="itemListenerAdapter" />
<property name="transactionManager" ref="jtaTransactionManager" />
<property name="sessionTransacted" value="true" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="3000" />
</bean>
I would like to have some initialization done before any message is received by the listener.
Can't you just use #PostConstruct to annotate a method on ItemMDPImpl to perform startup initialization, just like any other Spring bean?
4.9.6 #PostConstruct and #PreDestroy