I have been struggling through trying to build a very simple application that deploys to tomee using spring, hibernate and JMS. I believe that all of my configurations are correct (have the mysql xa data source and the xa active mq connection factory) but things aren't working as I would expect. Currently I have a simple service that writes using the injected entity manager then pushes to JMS within one method that is annotated as being transactional (spring annotation) but my message listener is being delivered these messages before the transaction is committed in spring.
I've tried using JMS template and the xa connection factory directly, but neither works properly. The template is configured with the jta transaction manager received from jndi. Any ideas what to look at for hints as to why the JMS sends are not participating in the same transaction as the database writes?
Spring Configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:encryption="http://www.jasypt.org/schema/encryption"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd
http://www.jasypt.org/schema/encryption http://www.jasypt.org/schema/encryption/jasypt-spring31-encryption-1.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd"
default-autowire="byType" default-lazy-init="false">
<context:component-scan annotation-config="false" base-package="org.superbiz" />
<bean class="org.springframework.orm.jpa.DefaultJpaDialect" />
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
<property name="alwaysUseJndiLookup" value="false" />
<property name="jndiFactory" >
<ref local="jndiFactory"/>
</property>
</bean>
<bean id="jndiFactory" class="org.springframework.jndi.support.SimpleJndiBeanFactory">
<property name="resourceRef" value="true" />
</bean>
<bean id="PrintTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="jmsFactory" />
</property>
<property name="defaultDestinationName" value="resources/jms/PrintQueue" />
<property name="deliveryPersistent" value="true"/>
<!-- <property name="sessionTransacted" value="true"/> -->
<!-- <property name="sessionAcknowledgeMode" value="0"/> -->
</bean>
<bean id="PersistTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="jmsFactory" />
</property>
<property name="defaultDestinationName" value="resources/jms/PersistQueue" />
<property name="deliveryPersistent" value="true"/>
<!-- <property name="sessionTransacted" value="true"/> -->
<!-- <property name="sessionAcknowledgeMode" value="0"/> -->
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
<property name="defaultPersistenceUnitName" value="movie-unit" />
<property name="persistenceContexts">
<map>
<entry key="movie-unit" value="persistence/movie-unit" />
</map>
</property>
</bean>
<context:component-scan base-package="org.superbiz.ejb" annotation-config="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<jee:jndi-lookup id="jmsFactory" jndi-name="resources/jms/ConnectionFactory" expected-type="javax.jms.ConnectionFactory" />
<tx:jta-transaction-manager />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="printBean" class="org.superbiz.mdb.PrintBean"/>
<bean id="persistBean" class="org.superbiz.mdb.PersistBean"/>
<jms:listener-container container-type="default" connection-factory="jmsFactory" cache="none" transaction-manager="transactionManager" concurrency="1" receive-timeout="1000" prefetch="-1">
<jms:listener destination="resources/jms/PrintQueue" ref="printBean" />
<jms:listener destination="resources/jms/PersistQueue" ref="persistBean" />
</jms:listener-container>
</beans>
tomee.xml (cobbled together from http://tomee-openejb.979440.n4.nabble.com/MDB-doesn-t-read-messages-td4666169.html)
<Resource id="ActiveMQResourceAdapter" type="ActiveMQResourceAdapter">
BrokerXmlConfig=broker:(vm://localhost)
</Resource>
<Resource id="resources/jms/ConnectionFactory" type="javax.jms.ConnectionFactory">
ResourceAdapter = ActiveMQResourceAdapter
</Resource>
<Resource id="resources/jms/XAConnectionFactory" class-name="org.apache.activemq.ActiveMQXAConnectionFactory">
BrokerURL = vm://localhost
ResourceAdapter = ActiveMQResourceAdapter
</Resource>
<Resource id="resources/jms/PrintQueue" type="javax.jms.Queue"/>
<Resource id="resources/jms/PersistQueue" type="javax.jms.Queue"/>
<Resource id="MySQL Database" type="DataSource">
JdbcDriver com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
JdbcUrl jdbc:mysql://localhost/test
UserName test
</Resource>
I've tried several ways to include not using the XAConnectionFactory with the JMSTemplate configured to be sessionTransacted and not, removing the JMSTemplate and creating the connection/session/producer/message from the connectionFactory, but I've run into an issue each time. With manually creating the connection/session/producer/message from the ConnectionFactory I notice that the 20 items I attempt to write to the database then send to another JMS Queue start being read before the services transaction completes.
As far as I can tell everything is configured correctly (although I could definitely be wrong since this was pulled from lots of places). My goal is to be able to use the JMSTemplate instead of manually creating the connection/session/etc myself, but I'm at a loss for why this is happening at this point so any ideas are greatly appreciated.
I've also bumped the logging of the Spring JtaTransactionManager up and I see the following happening when the @Transactional method is called
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [org.superbiz.ejb.Movies.send]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
Then I see the my MDB code trying to retrieve the item from the entityManager (which works/fails intermittently). When it succeeds I see this
printing from MDB: director: director0title: title0year: 0
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [org.springframework.jms.listener.DefaultMessageListenerContainer#0]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
When it fails I see this
/***************** BROKEN ***************/
/*******************435265*****************/
/***************** BROKEN ***************/
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [org.springframework.jms.listener.DefaultMessageListenerContainer#0]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
I've further cranked up the logging
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [org.springframework.jms.listener.DefaultMessageListenerContainer#1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
**bold** [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [org.springframework.jms.listener.DefaultMessageListenerContainer#0]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization
[org.springframework.jms.listener.DefaultMessageListenerContainer#0-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Initializing transaction synchronization
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.jms.connection.JmsResourceHolder@81032a4] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@5ee0c65d] to thread [org.springframework.jms.listener.DefaultMessageListenerContainer#1-1]
[org.springframework.jms.listener.DefaultMessageListenerContainer#0-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Bound value [org.springframework.jms.connection.JmsResourceHolder@eaebd86] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@5ee0c65d] to thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Getting transaction for [org.superbiz.mdb.PersistBean.onMessage]
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.jms.connection.JmsResourceHolder@81032a4] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@5ee0c65d] bound to thread [org.springframework.jms.listener.DefaultMessageListenerContainer#1-1]
Persisted finished, but not yet committed
Leaving persist, should commit
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Completing transaction for [org.superbiz.mdb.PersistBean.onMessage]
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.jta.JtaTransactionManager - Triggering beforeCommit synchronization
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.jta.JtaTransactionManager - Triggering beforeCompletion synchronization
[org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] TRACE org.springframework.transaction.support.TransactionSynchronizationManager - Removed value [org.springframework.jms.connection.JmsResourceHolder@81032a4] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@5ee0c65d] from thread [org.springframework.jms.listener.DefaultMessageListenerContainer#1-1]
**bold** [org.springframework.jms.listener.DefaultMessageListenerContainer#1-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
Entering print
/***************** BROKEN ***************/
/*******************597852*****************/
/***************** BROKEN ***************/
in this case DefaultMessageListenerContainer#1-1 is my Persist bean and 0-1 is a bean that retrieves the entity by id and then prints the content.
I am not 100% sure how to read this, but it's interesting to me that the DMLC#1-1 commits well after a new transaction has begun on DMLC#0-1, but DMLC#0-1 sees the message. I would have thought that DMLC#0-1 would have needed to start a new transaction to see this OR since he received the JMS message the entity should also be persisted to the database.
Contents of my persist bean @Override @Transactional public void onMessage(Message message) { TextMessage msg = (TextMessage)message; int x;
try {
x = Integer.parseInt(msg.getText());
Movie movie = new Movie("director" + x, "title" + x, x);
entityManager.persist(movie);
final long id = movie.getId();
template.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(Long.toString(id));
}
});
System.out.println("Persisted finished, but not yet committed");
System.out.println("Leaving persist, should commit");
} catch (Exception e) {
e.printStackTrace();
}
}
Contents of my print bean
public void onMessage(Message message) {
System.out.println("Entering print");
final TextMessage textMessage = (TextMessage) message;
try {
long id = Long.parseLong(textMessage.getText());
Movie movie = entityManager.find(Movie.class, id);
if(movie == null){
System.out.println("/***************** BROKEN ***************/");
System.out.println("/*******************" + id + "*****************/");
System.out.println("/***************** BROKEN ***************/");
} else {
System.out.println("updating: "+ movie);
}
} catch (Exception e) {
e.printStackTrace();
}
}
The entire app is available at https://github.com/jej2003/simple-spring, running a vanilla Tomee 1.7.1 with the necessary hibernate jars added to the tomee/lib directory.
I'm really at a loss here, does no one run JTA transactions in Tomee with Spring?
TRACElevel logging will expose a lot of information. If you can't figure it out from theTRACElogs, post them someplace. - Gary Russell