This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 877dfd3 CAMEL-16861: Cleanup and update EIP docs 877dfd3 is described below commit 877dfd30d7f321fd5ac8371b7a0c9141cc1e2327 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Oct 22 11:56:02 2021 +0200 CAMEL-16861: Cleanup and update EIP docs --- .../modules/eips/pages/transactional-client.adoc | 352 +++++++++------------ 1 file changed, 146 insertions(+), 206 deletions(-) diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc index e1f35c4..057493a 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc @@ -1,60 +1,101 @@ = Transactional Client -Camel recommends supporting the +Camel supports the http://www.enterpriseintegrationpatterns.com/TransactionalClient.html[Transactional Client] from the xref:enterprise-integration-patterns.adoc[EIP patterns] -using spring transactions. +using JTA transactions. + +How can a client control its transactions with the messaging system? 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. +Use a Transactional Client—make the client’s session with the messaging system transactional so that the client can specify transaction boundaries. -== Configuration of Redelivery +Transactions are supported by Spring Transactions and also with a JTA Transaction Manager. -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. +Traditionally a JTA Transaction Manager are included in JEE application servers. +However, when running microservice applications with Spring Boot, or Quarkus, then +a 3rd-party JTA transaction manager can be embedded and used. -You should use the -https://www.javadoc.io/doc/org.apache.camel/camel-spring/current/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. +In Camel transactions are supported by JMS messaging components: -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. +- xref:components::activemq-component.adoc[ActiveMQ] +- xref:components::jms-component.adoc[JMS] +- xref:components::sjms-component.adoc[Simple JMS] +- xref:components::sjms2-component.adoc[Simple JMS 2.x] -You first define needed object in the spring configuration. +And all the SQL database components, such as: -[source,xml] ----- -<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> - <property name="connectionFactory" ref="jmsConnectionFactory" /> -</bean> +- xref:components::elsql-component.adoc[ElSQL] +- xref:components::jdbc-component.adoc[JDBC] +- xref:components::jpa-component.adoc[JPA] +- xref:components::sql-component.adoc[SQL] +- xref:components::mybatis-component.adoc[MyBatis] -<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> - <property name="brokerURL" value="tcp://localhost:61616"/> -</bean> ----- +== Understanding Transactions + +A transaction is a series of events. The start of a transaction is often named begin, and +the end is commit (or rollback if the transaction isn’t successfully completed). -Then you look them up and use them to create the JmsComponent. +If you were to write in Java a locally managed transaction then it could be something like: [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); +TransactionManager tm = ... +Transaction tx = tm.getTransaction(); +try { + tx.begin(); + // code here under transaction + tx.commit(); +} catch (Exception e) { + tx.rollback(); +} ---- -[[TransactionalClient-TransactionPolicies]] -== Transaction Policies +You start the transaction using the `begin` method. Then you have a series of events to +do whatever work needs to be done. At the end, you either `commit` or `rollback` the +transaction, depending on whether an exception is thrown. + +You may already be familiar with this principle, and transactions in Camel use the +same principle at a higher level of abstraction. In Camel transactions, you don’t invoke +begin and commit methods from Java code; you use declarative transactions, which can +be configured using Java code or in XML files. Camel doesn't reinvent the wheel and +implement a transaction manager, which is a complicated piece of technology to build. +Instead, Camel uses APIs from either `camel-spring` or `camel-jta`. + +=== Local vs Global Transactions + +TODO: + +=== About Spring Transactions + +Camel uses Spring Transaction to manage transactions via its `TransactionManager` +API. Depending on the kinds of resources that are taking part in the transaction, +an appropriate implementation of the transaction manager must be chosen. Spring +offers a number of transaction managers out of the box that work for various local +transactions such as JMS and JDBC. But for global transactions, you must use a third-party +JTA transaction manager implementation; JTA transaction manager is provided +by Java EE application servers. Spring doesn't offer that out of the box, only the necessary +API abstract that Camel uses. + +=== About JTA Transactions + +TODO: + +== Using Transactions in Camel + +TODO: + +=== Transaction error handler + +When a route is marked as transacted using ``<transacted/>` Camel will +automatically use `TransactionErrorHandler` as the +xref:latest@manual:ROOT:error-handler.adoc[Error Handler]. + +This error handler supports basically the same +feature set as the xref:latest@manual:ROOT:defaulterrorhandler.adoc[DefaultErrorHandler]. + +=== 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 @@ -105,55 +146,53 @@ from("activemq:queue:foo").policy(notsupported) .to("activemq:queue:bar"); ---- -[[TransactionalClient-DatabaseSample]] -== Database Sample +== Transaction example with database 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/tree/main/components/camel-spring/src/test/java/org/apache/camel/spring/interceptor/TransactionalClientDataSourceMinimalConfigurationTest.java[unit test]. +https://github.com/apache/camel/tree/main/components/camel-spring-xml/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. +First we set up 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. +importantly the Spring `DataSourceTransactionManager` that is doing the +heavy lifting of ensuring our transactional policies. 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. +*standard* Spring beans only, there are no Camel specific configuration at all. + [source,xml] ---- - <!-- this example uses JDBC so we define a data source --> - <jdbc:embedded-database id="dataSource" type="DERBY"> - <jdbc:script location="classpath:sql/init.sql" /> - </jdbc:embedded-database> - - <!-- spring transaction manager --> - <!-- this is the transaction manager Camel will use for transacted routes --> - <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> - <property name="dataSource" ref="dataSource"/> - </bean> - - <!-- bean for book business logic --> - <bean id="bookService" class="org.apache.camel.spring.interceptor.BookService"> - <property name="dataSource" ref="dataSource"/> - </bean> +<!-- this example uses JDBC so we define a data source --> +<jdbc:embedded-database id="dataSource" type="DERBY"> + <jdbc:script location="classpath:sql/init.sql" /> +</jdbc:embedded-database> + +<!-- spring transaction manager --> +<!-- this is the transaction manager Camel will use for transacted routes --> +<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> + <property name="dataSource" ref="dataSource"/> +</bean> + +<!-- bean for book business logic --> +<bean id="bookService" class="org.apache.camel.spring.interceptor.BookService"> + <property name="dataSource" ref="dataSource"/> +</bean> ---- 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. +as transacted using the `<transacted/>` XML tag. [source,xml] ---- - <camelContext xmlns="http://camel.apache.org/schema/spring"> +<camelContext xmlns="http://camel.apache.org/schema/spring"> + <route> <from uri="direct:okay"/> <!-- we mark this route as transacted. Camel will lookup the spring transaction manager @@ -185,15 +224,15 @@ as transacted using the *transacted* tag. </setBody> <bean ref="bookService"/> </route> - </camelContext> + +</camelContext> ---- 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. +Just remember to use `<transacted/>`. The rest is standard Spring +XML to set up the transaction manager. -[[TransactionalClient-JMSSample]] -== JMS Sample +== Transaction example with JMS 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 @@ -207,148 +246,49 @@ use in our routing. [source,xml] ---- - <!-- setup JMS connection factory --> - <bean id="poolConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" init-method="start" destroy-method="stop"> - <property name="maxConnections" value="8"/> - <property name="connectionFactory" ref="jmsConnectionFactory"/> - </bean> - - <bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> - <property name="brokerURL" value="vm://localhost?broker.persistent=false&broker.useJmx=false"/> - </bean> - - <!-- setup spring jms TX manager --> - <bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> - <property name="connectionFactory" ref="poolConnectionFactory"/> - </bean> - - <!-- define our activemq component --> - <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent"> - <property name="connectionFactory" ref="poolConnectionFactory"/> - <!-- define the jms consumer/producer as transacted --> - <property name="transacted" value="true"/> - <!-- setup the transaction manager to use --> - <!-- if not provided then Camel will automatic use a JmsTransactionManager, however if you - for instance use a JTA transaction manager then you must configure it --> - <property name="transactionManager" ref="jmsTransactionManager"/> - </bean> ----- - -And then we configure our routes. Notice that all we have to do is mark the -route as transacted using the *transacted* tag. - -[source,xml] ----- - <camelContext xmlns="http://camel.apache.org/schema/spring"> - <!-- disable JMX during testing --> - <jmxAgent id="agent" disabled="true"/> - <route> - <!-- 1: from the jms queue --> - <from uri="activemq:queue:okay"/> - <!-- 2: mark this route as transacted --> - <transacted/> - <!-- 3: call our business logic that is myProcessor --> - <process ref="myProcessor"/> - <!-- 4: if success then send it to the mock --> - <to uri="mock:result"/> - </route> - </camelContext> - - <bean id="myProcessor" class="org.apache.camel.component.jms.tx.JMSTransactionalClientTest$MyProcessor"/> ----- - -=== Transaction error handler - -When a route is marked as transacted using *transacted* Camel will -automatic use `TransactionErrorHandler` as the -xref:latest@manual:ROOT:error-handler.adoc[Error Handler]. This error handler supports basically the same -feature set as the xref:latest@manual:ROOT:defaulterrorhandler.adoc[DefaultErrorHandler], -so you can for instance use xref:latest@manual:ROOT:exception-clause.adoc[Exception Clause] -as well. - -[[TransactionalClient-IntegrationTestingwithSpring]] -== 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. - -[[TransactionalClient-Usingmultiplerouteswithdifferentpropagationbehaviors]] -== Using multiple routes with different propagation behaviors - -*Since Camel 2.2* - -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: +<!-- setup JMS connection factory --> +<bean id="poolConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" init-method="start" destroy-method="stop"> + <property name="maxConnections" value="8"/> + <property name="connectionFactory" ref="jmsConnectionFactory"/> +</bean> -* The first route use `PROPAGATION_REQUIRED` -* The second route use `PROPAGATION_REQUIRES_NEW` +<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> + <property name="brokerURL" value="vm://localhost?broker.persistent=false&broker.useJmx=false"/> +</bean> -This is configured in the Spring XML file: +<!-- setup spring jms TX manager --> +<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> + <property name="connectionFactory" ref="poolConnectionFactory"/> +</bean> -[source,xml] ----- - <bean id="PROPAGATION_REQUIRED" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> - <property name="transactionManager" ref="txManager"/> - <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/> - </bean> - - <bean id="PROPAGATION_REQUIRES_NEW" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> - <property name="transactionManager" ref="txManager"/> - <property name="propagationBehaviorName" value="PROPAGATION_REQUIRES_NEW"/> - </bean> +<!-- define our activemq component --> +<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent"> + <property name="connectionFactory" ref="poolConnectionFactory"/> + <!-- define the jms consumer/producer as transacted --> + <property name="transacted" value="true"/> + <!-- setup the transaction manager to use --> + <!-- if not provided then Camel will automatic use a JmsTransactionManager, however if you + for instance use a JTA transaction manager then you must configure it --> + <property name="transactionManager" ref="jmsTransactionManager"/> +</bean> ---- -Then in the routes you use transacted DSL to indicate which of these two -propagations it uses. +And then we configure our routes. Notice that all we have to do is mark the +route as transacted using the `<transacted/>` XML tag. -[source,java] +[source,xml] ---- - from("direct:mixed") - // using required - .transacted("PROPAGATION_REQUIRED") - // all these steps will be okay - .setBody(constant("Tiger in Action")).bean("bookService") - .setBody(constant("Elephant in Action")).bean("bookService") - // continue on route 2 - .to("direct:mixed2"); - - from("direct:mixed2") - // tell Camel that if this route fails then only rollback this last route - // by using (rollback only *last*) - .onException(Exception.class).markRollbackOnlyLast().end() - // using a different propagation which is requires new - .transacted("PROPAGATION_REQUIRES_NEW") - // this step will be okay - .setBody(constant("Lion in Action")).bean("bookService") - // this step will fail with donkey - .setBody(constant("Donkey in Action")).bean("bookService"); +<camelContext xmlns="http://camel.apache.org/schema/spring"> + <route> + <!-- 1: from the jms queue --> + <from uri="activemq:queue:okay"/> + <!-- 2: mark this route as transacted --> + <transacted/> + <!-- 3: call our business logic that is myProcessor --> + <process ref="myProcessor"/> + <!-- 4: if success then send it to the mock --> + <to uri="mock:result"/> + </route> +</camelContext> ---- -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. -