1
votes

I am using Spring Integration 4.1.5 and trying to do something with transactions, but unfortunately am not able to and could not find working examples. I'm trying to setup a JMS poller that is looking for messages. Once a message is received, a service activator will insert a row into the database, and the message is passed to another service activator. I would like to make the first two pieces, the message pickup & database insert transactional. I do not want the rest of the flow to be transactional. I am using Weblogic as the application container so will be using the WebLogicJtaTransactionManager.

The problem I'm running into is I am unable to make the first two pieces transactional. It's either all, or nothing. I've tried many methods, but I feel like using an advice-chain on the poller is the best bet. I would be able to control which methods would be part of the transaction.

I've seen examples using a message driven listener, but I am using Weblogic and would be using the Work Managers and I believe I must use a poller in order to take advantage of work managers (if that's not the case, I guess that's another question for the future!)

I've taken the xml and simplified it, but besides editing out the package name, the context produces the issue.

   <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
    xmlns:file="http://www.springframework.org/schema/integration/file"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
    xmlns:int-xml="http://www.springframework.org/schema/integration/xml"
    xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd 
    http://www.springframework.org/schema/integration 
    http://www.springframework.org/schema/integration/spring-integration.xsd 
    http://www.springframework.org/schema/integration/file 
    http://www.springframework.org/schema/integration/file/spring-integration-file.xsd 
    http://www.springframework.org/schema/integration/sftp 
    http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd 
    http://www.springframework.org/schema/integration/xml 
    http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd 
    http://www.springframework.org/schema/integration/jms 
    http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="jtaTransactionManager"
        class="org.springframework.transaction.jta.WebLogicJtaTransactionManager">
        <property name="transactionManagerName" value="javax.transaction.TransactionManager" />
    </bean>

    <bean id="insertMessageToDb" class="com.ReadMsgFromAxway" />
    <bean id="serviceActivator" class="com.CreateTMDFile" />

    <int-jms:inbound-channel-adapter id="jmsDefaultReceiver"
        connection-factory="inboundDefaultAdaptorConnectionFactory"
        extract-payload="false" destination="inboundAdaptorDefaultListenerQueue"
        channel="inboundJMS" acknowledge="transacted">  
        <int:poller id="poller" 
            max-messages-per-poll="100" fixed-rate="10">
            <int:advice-chain>
                <ref bean="txAdvice" />
            </int:advice-chain>
        </int:poller>
    </int-jms:inbound-channel-adapter>


    <tx:advice id="txAdvice" transaction-manager="jtaTransactionManager">
        <tx:attributes>
            <tx:method name="processMessage" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="txOperation" 
            expression="execution(* axway.ReadMsgFromAxway.processMessage(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txOperation" />
    </aop:config> 

    <int:service-activator input-channel="inboundJMS"
        output-channel="serviceActivatorChannel" ref="insertMessageToDb" method="processMessage" />

    <int:chain input-channel="serviceActivatorChannel" output-channel="nullChannel">
        <int:service-activator ref="serviceActivator" />
    </int:chain>

</beans>

ReadMsgFromAxway.java

    public Message<File> processMessage(Message<?> message)  {
//Insert into DB
        trackerProcess.insertUpdateMessageTracker(message, "axwayChannel",
                "axwayChannel", currentStatusID, null, null);
        count++;
        int mod = count % 2;
        if (mod != 0) {
            // pass every 2
            String hello = "hey";
        } else {
            throw new RuntimeException("Testing transactional");
        }

        Message<File> springMessage = MessageBuilder.createMessage(payloadFile,
                messageHeaders);
        return springMessage;
    }

The XML as is doesn't do anything, whether the runtime exception is thrown, or an exception is thrown at the next service activator component.

If I change the advice attributes to

        <tx:method name="*" propagation="REQUIRED"/>

Then the exceptions at the 1st and 2nd service activator causes the rollback.

Oddly enough if I do this

        <tx:method name="processMessage" propagation="REQUIRED"/>
        <tx:method name="*" propagation="NEVER"/>

Then whether the runtime exception in the 1st service activator, or the 2nd activator is thrown, the message gets rolled back. I would think the pointcut would limit which class would cause the transaction, but I maybe I'm misunderstanding something.

Another note - THis application is housed in an ear file with several other wars. This context starts the entire inbound process, and connects to another war, which contains business logic, via a JMS queue. In the scenario of using method name="*" I saw the exceptions in the business logic war to cause the JMS message of the original inbound message get rolled back too. I was under the impression that the 2nd war would do its processing in another thread, since it receives a message via a queue, and thus not be part of the transaction. Could this be a side effect of JTA, which is container managed?

Thanks!

1

1 Answers

0
votes

I'd recommend you to read Dave Syer's article about transaction and there you can find a link in the "More like this".

Right now it looks like you don't understand transactions and AOP at all. You should pay more attention to AOP support in Spring Framework.

In general declarative transactions (@Transactional on method) are the particular cases of AOP advices. The main concept behind any AOP is call stack boundary spawned by the method invocation for which we specify an advice.

But if there is no such a method in the target object, it won't be adviced and wrapped to an AOP proxy. Like in your case for the processMessage when you apply the txAdvice for some internal object around the org.springframework.messaging.Message.MessageSource as a contract of the JmsDestinationPollingSource for that <int-jms:inbound-channel-adapter>.

Instead you can use <transactional> configuration of the <poller>. And right: all the donwstream flow will be covered by transaction.

To finish transaction in this case for that internal

    Callable<Boolean> pollingTask = new Callable<Boolean>() {

        @Override
        public Boolean call() throws Exception {
            return doPoll();
        }
    };

around receive() and handleMessage(), you should just finish the method invocation as in regular @Transactional case. For this purpose we should shit the next message handling to the different thread. Just because by default all <channel>s are DirectChannel and tied with the current call stack.

For this purpose you can just use an ExecutorChannel (<int:dispatcher task-executor="threadPoolExecutor"/>) for that your serviceActivatorChannel.

From there you don't need the rest AOP configuration.

Not sure about your second question for an other web app. Looks like it has some logic to rallback its work in case of some inconsistency from your side. But anyway that looks like a different question.