Search code examples
transactionsesbmulerollback

Mule ESB 3.3 Exception Strategies & Rollback baffling behaviour


Setup:
Tabe: CREATE TABLE test (id int PRIMARY KEY NOT NULL, name varchar(25));

Basic test flow:

enter image description here

Preconditions: The database already has a record with id=2 (to force SqlException)

Case 1:

Whe basic flow is invoked as is, the second insert fails due to primary key violation, and the first insert gets rolled back. Seems like a reasonable default behaviour.

Case 2:

Modify base flow by adding a Rollback Exception Strategy and add a logger inside the exception strategy to print something when it gets called.
When flow is invoked, the second insert fails due to primary key violation, and the first insert gets rolled back but the RollbackExceptionStrategy never gets called!
Hardly what anyone would expect.

So the question here is: Why doesn't the rollback exception strategy gets called and what I need to do to get it called?

Case 3:

Modify base flow by adding a Catch Exception Strategy and add a logger inside the exception strategy to print something when it gets called.
When flow is invoked, the second insert fails due to primary key violation. This time, the exception strategy is called but now the transaction was not rolled back.

The question here is: Why is the transaction not being rolled back and how to force a rollback inside an exception strategy different from a rollback strategy?

Any help would be greatly appreciated


Edit1: Here is the complete xml for the flow (only basic case):

<mule>
    <spring:beans>
        <spring:bean id="dataSource" name="dataSource" class="org.enhydra.jdbc.standard.StandardDataSource" destroy-method="shutdown">
            <spring:property name="driverName" value="org.h2.Driver" />
            <spring:property name="url" value="jdbc:h2:tcp://localhost/~/mule" />
            <spring:property name="user" value="sa" />
            <spring:property name="password">
                <spring:value></spring:value>
            </spring:property>
        </spring:bean>

        <spring:bean id="transactionFactory" name="transactionFactory" class="org.mule.transport.jdbc.JdbcTransactionFactory" />

    </spring:beans>

    <jdbc:connector name="dbConnector" dataSource-ref="dataSource" validateConnections="true" queryTimeout="-1" pollingFrequency="0" doc:name="Database" />

    <flow name="TriggerFlow" doc:name="TriggerFlow">
        <http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="8081" doc:name="HTTP" />
        <vm:outbound-endpoint exchange-pattern="request-response" path="txFlow" doc:name="VM" />
    </flow>

    <flow name="TxFlow" doc:name="case1Flow">
        <vm:inbound-endpoint exchange-pattern="request-response" path="txFlow" doc:name="case1">
            <custom-transaction factory-ref="transactionFactory" action="ALWAYS_BEGIN" timeout="10" />
        </vm:inbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert" queryTimeout="-1" connector-ref="dbConnector" doc:name="insert into test values (1, 'Test 1')">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert" value="insert into test values (1, 'Test 1')" />
        </jdbc:outbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert2" queryTimeout="-1" connector-ref="dbConnector" doc:name="insert into test values (2, 'Test 2')">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert2" value="insert into test values (2, 'Test 2')" />
        </jdbc:outbound-endpoint>
    </flow>

</mule>

Edit2: As it turns out, case 2 in isolation works, but it doesn't work in a more complex flow for example:

<mule>
    <spring:beans>
        <spring:bean id="dataSource" name="dataSource" class="org.enhydra.jdbc.standard.StandardDataSource" destroy-method="shutdown">
            <spring:property name="driverName" value="org.h2.Driver" />
            <spring:property name="url" value="jdbc:h2:tcp://localhost/~/mule" />
            <spring:property name="user" value="sa" />
            <spring:property name="password">
                <spring:value></spring:value>
            </spring:property>
        </spring:bean>

        <spring:bean id="transactionFactory" name="transactionFactory" class="org.mule.transport.jdbc.JdbcTransactionFactory" />

    </spring:beans>

    <jdbc:connector name="dbConnector" dataSource-ref="dataSource" validateConnections="true" queryTimeout="-1" pollingFrequency="0" doc:name="Database" />

    <flow name="TriggerTxFlow" doc:name="TriggerTxFlow">
        <http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="8081" doc:name="HTTP" />
        <set-variable variableName="flow" value="#[message.inboundProperties['http.query.params']['flow']]" doc:name="Variable"/>
        <vm:outbound-endpoint exchange-pattern="request-response" path="#[flow]" doc:name="VM" />
    </flow>

    <flow name="case1Flow" doc:name="case1Flow">
        <vm:inbound-endpoint exchange-pattern="request-response" path="case1" doc:name="case1">
            <custom-transaction factory-ref="transactionFactory" action="ALWAYS_BEGIN" timeout="10" />
        </vm:inbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert" queryTimeout="-1" connector-ref="dbConnector" doc:name="insert into test values (1, 'Test 1')">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert" value="insert into test values (1, 'Test 1')" />
        </jdbc:outbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert2" queryTimeout="-1" connector-ref="dbConnector" doc:name="insert into test values (2, 'Test 2')">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert2" value="insert into test values (2, 'Test 2')" />
        </jdbc:outbound-endpoint>
    </flow>

    <flow name="case2Flow" doc:name="case2Flow">
        <vm:inbound-endpoint exchange-pattern="request-response" path="case2" doc:name="case2">
            <custom-transaction factory-ref="transactionFactory" action="ALWAYS_BEGIN" timeout="10" />
        </vm:inbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert" queryTimeout="-1" connector-ref="dbConnector" doc:name="Database">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert" value="insert into test values (1, 'Test 1')" />
        </jdbc:outbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert2" queryTimeout="-1" connector-ref="dbConnector" doc:name="Database">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert2" value="insert into test values (2, 'Test 2')" />
        </jdbc:outbound-endpoint>
        <rollback-exception-strategy doc:name="Rollback Exception Strategy" enableNotifications="false" maxRedeliveryAttempts="0">
            <on-redelivery-attempts-exceeded doc:name="Redelivery exhausted">
                <logger message="========= Inside Exception Strategy =========" level="INFO" doc:name="Logger"/>
            </on-redelivery-attempts-exceeded>
        </rollback-exception-strategy>
    </flow>

    <flow name="case3Flow" doc:name="case3Flow">
        <vm:inbound-endpoint exchange-pattern="request-response" path="case3" doc:name="VM">
            <custom-transaction factory-ref="transactionFactory" action="ALWAYS_BEGIN" timeout="10" />
        </vm:inbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert" queryTimeout="-1" connector-ref="dbConnector" doc:name="Database">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert" value="insert into test values (1, 'Test 1')" />
        </jdbc:outbound-endpoint>
        <jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="insert2" queryTimeout="-1" connector-ref="dbConnector" doc:name="Database">
            <jdbc:transaction action="ALWAYS_JOIN" />
            <jdbc:query key="insert2" value="insert into test values (2, 'Test 2')" />
        </jdbc:outbound-endpoint>
        <catch-exception-strategy doc:name="Catch Exception Strategy">
            <logger message="========= Inside Exception Strategy =========" level="INFO" doc:name="Logger"/>
        </catch-exception-strategy>
    </flow>

</mule>

Solution

  • Case 1: Yay!

    Case 2:

    Why doesn't the rollback exception strategy gets called and what I need to do to get it called?

    According to the doc the strategy should be called. It is maybe a configuration issue on your side because if I add:

    <rollback-exception-strategy>
        <logger message="---> In Rollback exception strategy!!!" />
    </rollback-exception-strategy>
    

    to "TxFlow" I can see the text being logged in the console.

    Case 3:

    Why is the transaction not being rolled back?

    That is the main purpose of the Catch Exception Strategy, from the doc:

    ensure that a transaction processed by the flow will not be rolled back when an error occurs (i.e. The transaction is never “rolled back” to reattempt processing; Mule commits the transaction.)

    How to force a rollback inside an exception strategy different from a rollback strategy

    I don't think you can. You can try throwing an exception from within the strategy but I'm afraid the transaction is already committed at that time.