1

My route looks like below

from("jms:queue:IN_QUEUE) //(A) Transactional Endpoint .transacted("required") //(B) TX Policy with PROPAGATION_REQUIRED and JPATxManager .bean("someBean", "readFromDB()") //(C) Read from DB .bean("someBean", "writeToDB()") //(D) Write to DB .to("file:/home/src?fileName=demo_${id}.txt")

I know the JMS consumer at (A) will fork out JMS Transaction on each poll and attaches to the thread. Also the transacted node in (B) will fork out JPA transaction after an exchange reaches there and attaches to the thread.

Please find my questions below:

  1. Can two different transactions get attached to a single thread (like the one above) ?
  2. If Yes, which one should get suspended ?
  3. What should be the commit and rollback order of the above mentioned route ?

    Note: I didn't find any obvious answer from Camel In Action 2nd Ed book, So please guide me

Raju Parashar
  • 312
  • 4
  • 17

2 Answers2

2

Good afternoon,

This is a variation on your other question.

The:

from("jms:queue:IN_QUEUE)      //(A) Transactional Endpoint

endpoint is transacted, meaning that you have marked the JMS component as transacted and the JMS sessions will be managed by a JmsTransactionManager.

.transacted("required")   //(B) TX Policy with PROPAGATION_REQUIRED and 
JPATxManager

This should not be a JPA transaction manager, but a JTA transaction manager (like Arjuna). As in your other question, you now have a JMS local transaction for reading you message, and local JPA transacted sessions for your DB access. You want the PlatformTransactionManager (a JTA transaction manager) to synchronize the local transactions for you.

As to your questions:

Can two different transactions get attached to a single thread (like the one above) ?

That really doesn't make any sense.

If Yes, which one should get suspended ?

Nothing will get suspended.

What should be the commit and rollback order of the above mentioned route ?

The DB read is not transactional and does not need to be committed. The file write will actually happen as the JTA transactional context is closed. This leaves the DB write. If that fails, then the DB read does not matter, the message will get put back on the source destination and the file write will not be called.

Enabling the DEBUG logging for the various transaction managers is very helpful.

I could go on about this with this in painful detail. This is more for burki. I think that you will really appreciate this. Very subtle, and it happens a lot.

   from("jms:queue:SRC_QUEUE")
     .transacted("required")
     .to("jms1:queue:DEST_QUEUE") 

What happens if the two endpoints are marked as transacted ... but... you do not have the 'transacted' line? Well, a JMS local transaction was started on the message listener. This will be committed as the route ends. There are two independent local JMS transactions. These are not synchronized by a JTA transaction manager.

What actually happens is that the commit for the message 'get' is called. There is no actual commit for the message 'put'. The message 'put' is committed when the JMS session is closed. This is in the JMS spec, that closing the connection inherently commits any transaction. So, because there is no linkage between the two components, the 'get' is committed, and then the 'put' session is closed.

This means that you can lose messages if there is an outage between the commit for the message 'get' and the session close for the message 'put'.

Does that make sense? There is no linkage between the local transactions, so Camel closes them in order, starting with committing the 'get' before calling the 'put'.

JTA transaction synchronization is the key. You still have local transaction resources (not XA), but they can be very well managed in a pretty lightweight JTA transactional context.

   from("jms:queue:SRC_QUEUE")
     .transacted("required")
     .to("DB:transactedwrite")
     .to("jms1:queue:DEST_QUEUE") 

I couldn't be bothered to look up the correct syntax for a database insert, but you get the idea. In this case, you can get duplicate DB inserts if the JMS 'put' fails. This is not 'all or nothing' XA transactions. The transactions are committed in order. If one in the middle succeeds, then the next transaction fails, well the 'get' will get rolled back and you will get duplicates up to the point of failure.

Doug Grove
  • 962
  • 5
  • 5
  • Do I understand this correct, that when I use Spring JTA Tx manager and I am **not** on an Application Server, Spring has no XA but does its best to optimize the handling of local transactions and therefore the potential message loss you describe between two different brokers is "fixed"? – burki Mar 11 '20 at 16:43
  • 1
    The Spring PlatformTransactionManager is just an interface that requires an implementation. For example, if you were not in an Application Server, but were using Spring Boot, you could include the spring-boot-starter-jta-narayana starter. This would wire in Arjuna as the PlatformTransactionManager, and you could have JTA synchronised transactions. – Doug Grove Mar 11 '20 at 17:22
  • OK, it is not Spring itself. That means that JTA implementations are able to use local resources (non-XA-resources) in a distributed transaction and do a better job regarding synchronization than Camel with two local transactions. I.e. in the JMS example you describe the lost message would not be possible with JTA. – burki Mar 12 '20 at 11:27
0

Sorry, I can't answer your specific questions, but I can give some specific infos about the transactions of your route.

You've got 3 different "systems" with different transaction "scopes"

  • A JMS broker from which you consume
  • A database you read and write from and you configured a JPA TxManager for
  • A file system (no transactions at all) as destination

First of all, if you want to have transaction safety across JMS and the database you have to use XA transactions.

Then, it is unclear if you expect the JMS consumer to be transacted (because of the transacted() in your route) or if you really configured a JMS connection with local JMS transactions. I assume that you really consume transacted.

Let's talk about what you got without line B of your route:

  • You consume transacted from the broker
  • Camel processes the message through your route
  • When any error occurs during route processing, the message is not committed on the broker and therefore redelivered to your route

The transaction that is opened by a transactional consumer is kept open by Camel until the route is successfully processed.

So the only obvious problem would be an error after the database write, that triggers a redelivery and the database write is done once again. Probably the write is not idempotent and therefore must not happen twice.

So to solve these problems, you either have to use XA transactions or you simply use local JMS transactions and implement compensation logic for the "gaps" like the one described above.

The database transaction on the other hand has no benefit unless the read and write operation must be done in a transaction (but I have doubts that this is the case with two individual bean calls and a JMS consumer).

burki
  • 6,741
  • 1
  • 15
  • 31
  • If I remove transacted mentioned in (B), will (C) and (D) happen in a transaction ? I hope No, because transaction forked out by JmsConsumer in (A) cann't commit JPA entities. – Raju Parashar Mar 09 '20 at 13:18
  • Does your JMS consumer use local JMS transactions? I.e. no Spring Tx Manager needed, just the JMS connection configured as transacted. – burki Mar 09 '20 at 14:19