blob: 767fa073ade354668f4a6318790f0c214ae24f95 [file] [log] [blame]
[[rollback-eip]]
= Rollback EIP
:page-source: core/camel-core-engine/src/main/docs/eips/rollback-eip.adoc
Rollback might be needed if there is a transaction or transactional pieces in your design.
Camel recommends supporting the
http://www.enterpriseintegrationpatterns.com/TransactionalClient.html[Transactional
Client] from the xref:enterprise-integration-patterns.adoc[EIP patterns]
using spring transactions.
image::eip/TransactionalClientSolution.gif[image]
Transaction Oriented Endpoints like xref:components::jms-component.adoc[JMS] support using a
transaction for both inbound and outbound message exchanges. Endpoints
that support transactions will participate in the current transaction
context that they are called from.
Configuration of Redelivery
The redelivery in transacted mode is *not* handled by Camel but by the
backing system (the transaction manager). In such cases you should
resort to the backing system how to configure the redelivery.
You should use the
http://camel.apache.org/maven/current/camel-spring/apidocs/org/apache/camel/spring/SpringRouteBuilder.html[SpringRouteBuilder]
to setup the routes since you will need to setup the spring context with
the TransactionTemplates that will define the transaction manager
configuration and policies.
For inbound endpoint to be transacted, they normally need to be
configured to use a Spring PlatformTransactionManager. In the case of
the JMS component, this can be done by looking it up in the spring
context.
You first define needed object in the spring configuration.
[source,xml]
--------------------------------------------------------
<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
--------------------------------------------------------
Then you look them up and use them to create the JmsComponent.
[source,java]
----
PlatformTransactionManager transactionManager = (PlatformTransactionManager) spring.getBean("jmsTransactionManager");
ConnectionFactory connectionFactory = (ConnectionFactory) spring.getBean("jmsConnectionFactory");
JmsComponent component = JmsComponent.jmsComponentTransacted(connectionFactory, transactionManager);
component.getConfiguration().setConcurrentConsumers(1);
ctx.addComponent("activemq", component);
----
== Options
// eip options: START
The Rollback EIP supports 3 options which are listed below:
[width="100%",cols="2,5,^1,2",options="header"]
|===
| Name | Description | Default | Type
| *markRollbackOnly* | Mark the transaction for rollback only (cannot be overruled to commit) | false | Boolean
| *markRollbackOnlyLast* | Mark only last sub transaction for rollback only. When using sub transactions (if the transaction manager support this) | false | Boolean
| *message* | Message to use in rollback exception | | String
|===
// eip options: END
=== Transaction Policies
Outbound endpoints will automatically enlist in the current transaction
context. But what if you do not want your outbound endpoint to enlist in
the same transaction as your inbound endpoint? The solution is to add a
Transaction Policy to the processing route. You first have to define
transaction policies that you will be using. The policies use a spring
TransactionTemplate under the covers for declaring the transaction
demarcation to use. So you will need to add something like the following
to your spring xml:
[source,xml]
--------------------------------------------------------
<bean id="PROPAGATION_REQUIRED" class="org.apache.camel.spring.spi.SpringTransactionPolicy">
<property name="transactionManager" ref="jmsTransactionManager"/>
</bean>
<bean id="PROPAGATION_REQUIRES_NEW" class="org.apache.camel.spring.spi.SpringTransactionPolicy">
<property name="transactionManager" ref="jmsTransactionManager"/>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRES_NEW"/>
</bean>
--------------------------------------------------------
Then in your
http://camel.apache.org/maven/current/camel-spring/apidocs/org/apache/camel/spring/SpringRouteBuilder.html[SpringRouteBuilder],
you just need to create new SpringTransactionPolicy objects for each of
the templates.
[source,java]
----
public void configure() {
...
Policy required = bean(SpringTransactionPolicy.class, "PROPAGATION_REQUIRED"));
Policy requirenew = bean(SpringTransactionPolicy.class, "PROPAGATION_REQUIRES_NEW"));
...
}
----
Once created, you can use the Policy objects in your processing routes:
[source,java]
----
// Send to bar in a new transaction
from("activemq:queue:foo").policy(requirenew).to("activemq:queue:bar");
// Send to bar without a transaction.
from("activemq:queue:foo").policy(notsupported).to("activemq:queue:bar");
----
=== OSGi Blueprint
If you are using xref:using-osgi-blueprint-with-camel.adoc[OSGi
Blueprint] then you most likely have to explicit declare a policy and
refer to the policy from the transacted in the route.
[source,xml]
--------------------------------------------------------
<bean id="required" class="org.apache.camel.spring.spi.SpringTransactionPolicy">
<property name="transactionManager" ref="jmsTransactionManager"/>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
--------------------------------------------------------
And then refer to "required" from the route:
[source,xml]
--------------------------------------------------------
<route>
<from uri="activemq:queue:foo"/>
<transacted ref="required"/>
<to uri="activemq:queue:bar"/>
</route>
--------------------------------------------------------
== Database Sample
In this sample we want to ensure that two endpoints is under transaction
control. These two endpoints inserts data into a database. +
The sample is in its full as a
https://github.com/apache/camel/blob/master/components/camel-spring/src/test/java/org/apache/camel/spring/interceptor/TransactionalClientDataSourceMinimalConfigurationTest.java[unit
test].
First of all we setup the usual spring stuff in its configuration file.
Here we have defined a DataSource to the HSQLDB and a most
importantly the Spring DataSource TransactionManager that is doing the
heavy lifting of ensuring our transactional policies. You are of course
free to use any of the Spring based TransactionManager, eg. if you are
in a full blown J2EE container you could use JTA or the WebLogic or
WebSphere specific managers.
As we use the new convention over configuration we do *not* need to
configure a transaction policy bean, so we do not have any
`PROPAGATION_REQUIRED` beans. All the beans needed to be configured is
*standard* Spring beans only, eg. there are no Camel specific
configuration at
all.https://github.com/apache/camel/blob/master/components/camel-spring/src/test/resources/org/apache/camel/spring/interceptor/springTransactionalClientDataSourceMinimalConfiguration.xml[springTransactionalClientDataSourceMinimalConfiguration]Then
we are ready to define our Camel routes. We have two routes: 1 for
success conditions, and 1 for a forced rollback condition. +
This is after all based on a unit test. Notice that we mark each route
as transacted using the *transacted*
tag.https://github.com/apache/camel/blob/master/components/camel-spring/src/test/resources/org/apache/camel/spring/interceptor/springTransactionalClientDataSourceMinimalConfiguration.xml[springTransactionalClientDataSourceMinimalConfiguration]That
is all that is needed to configure a Camel route as being transacted.
Just remember to use the *transacted* DSL. The rest is standard Spring
XML to setup the transaction manager.
== JMS Sample
In this sample we want to listen for messages on a queue and process the
messages with our business logic java code and send them along. Since
its based on a https://github.com/apache/camel/blob/master/components/camel-jms/src/test/java/org/apache/camel/component/jms/tx/TransactionMinimalConfigurationTest.java[TransactionMinimalConfigurationTest.java] the destination is a mock endpoint.
First we configure the standard Spring XML to declare a JMS connection
factory, a JMS transaction manager and our ActiveMQ component that we
use in our
routing.https://github.com/apache/camel/blob/master/components/camel-jms/src/test/resources/org/apache/camel/component/jms/tx/TransactionMinimalConfigurationTest.xml[TransactionMinimalConfigurationTest.xml]And
then we configure our routes. Notice that all we have to do is mark the
route as transacted using the *transacted*
tag.https://github.com/apache/camel/blob/master/components/camel-jms/src/test/resources/org/apache/camel/component/jms/tx/TransactionMinimalConfigurationTest.xml[TransactionMinimalConfigurationTest.xml]
Transaction error handler
When a route is marked as transacted using *transacted* Camel will
automatic use the
xref:transactionerrorhandler.adoc[TransactionErrorHandler] as
xref:error-handler.adoc[Error Handler]. It supports basically the same
feature set as the xref:defaulterrorhandler.adoc[DefaultErrorHandler],
so you can for instance use xref:exception-clause.adoc[Exception Clause]
as well.
== Integration Testing with Spring
An Integration Test here means a test runner class annotated
`@RunWith(SpringJUnit4ClassRunner.class).`
When following the Spring Transactions documentation it is tempting to
annotate your integration test with `@Transactional` then seed your
database before firing up the route to be tested and sending a message
in. This is incorrect as Spring will have an in-progress transaction,
and Camel will wait on this before proceeding, leading to the route
timing out.
Instead, remove the `@Transactional` annotation from the test method and
seed the test data within a `TransactionTemplate` execution which will
ensure the data is committed to the database before Camel attempts to
pick up and use the transaction manager. A simple
example https://github.com/rajivj2/example2/blob/master/src/test/java/com/example/NotificationRouterIT.java[can
be found on GitHub].
Spring's transactional model ensures each transaction is bound to one
thread. A Camel route may invoke additional threads which is where the
blockage may occur. This is not a fault of Camel but as the programmer
you must be aware of the consequences of beginning a transaction in a
test thread and expecting a separate thread created by your Camel route
to be participate, which it cannot. You can, in your test, mock the
parts that cause separate threads to avoid this issue.
== Using multiple routes with different propagation behaviors
Suppose you want to route a message through two routes and by which the
2nd route should run in its own transaction. How do you do that? You use
propagation behaviors for that where you configure it as follows:
* The first route use `PROPAGATION_REQUIRED`
* The second route use `PROPAGATION_REQUIRES_NEW`
This is configured in the Spring XML
file.https://github.com/apache/camel/blob/master/components/camel-spring/src/test/resources/org/apache/camel/spring/interceptor/MixedTransactionPropagationTest.xml[MixedTransactionPropagationTest.xml]Then
in the routes you use transacted DSL to indicate which of these two
propagations it
uses.https://github.com/apache/camel/blob/master/components/camel-spring/src/test/java/org/apache/camel/spring/interceptor/MixedTransactionPropagationTest.java[MixedTransactionPropagationTest.java]Notice
how we have configured the `onException` in the 2nd route to indicate in
case of any exceptions we should handle it and just rollback this
transaction. This is done using the `markRollbackOnlyLast` which tells
Camel to only do it for the current transaction and not globally.