Instead of sending single message in a transaction:
jmsTemplate.convertAndSend(message);
How can I send multiple jms messages in a single transaction?
Is there an example I can loot at?
Start the transaction before calling the template
#Transactional
public void doSends() {
template.convertAndSend(...)
...
template.convertAndSend(...)
}
The transaction commits when the method exits. See the Spring documentation about transactions.
Or, use one the of the template's execute() methods and do the sends in the callback.
Related
I'm trying to understand how transaction works in Spring AMQP. Reading the docs: https://docs.spring.io/spring-amqp/reference/html/#transactions , I know the purpose for enabling transaction in publisher (Best Effort One Phase Commit pattern) but I have no idea why it could be necessary in MessageListener?
Let's take an example:
acknowledgeMode=AUTO
Consume message using #RabbitListener
Insert data into database
Publish message using rabbitTemplate
According to docs: https://docs.spring.io/spring-amqp/reference/html/#acknowledgeMode, if acknowledgeMode is set to AUTO then if any next operation fails, listener will fail too and message will be returned to queue.
Another question is whats the difference between local transaction and external transaction in that case (setting container.setTransactionManager(transactionManager()); or not)?
I would appreciate some clarification :)
Enable transactions in the listener so that any/all downstream RabbitTemplate operations participate in the same transaction.
If there is a failure, the container will rollback the transaction (removing the publishes), nack the message (or messages if batch size is greater than one) and then commit the nacks so the message(s) will be redelivered.
When using an external transaction manager (such as JDBC), the container will synchronize the AMQP transaction with the external transaction.
Downstream templates participate in the transaction regardless of whether it is local (AMQP only) or synchronized.
Consider the following rough example, where Spring is managing transactions as per a standard Spring Boot setup.
The default transaction manager provided by spring-boot-starter-data-jpa is used.
In this transactional unit of work, both database and JMS operations occur.
#Autowired JmsMessageOperations jmsMessageOperations;
#Autowired EntityManager entityManager;
#Transactional
public void doWork() {
entityManager.persist(new Foo(1));
entityManager.flush();
jmsMessageOperations.convertAndSend(BAR_QUEUE, new Bar(1));
entityManager.persist(new Foo(2));
entityManager.flush();
BazResponse bazResponse =
jmsMessageOperations.convertSendAndReceive(
BAZ_QUEUE,
new Baz(1),
BazResponse.class);
// here
}
What happens to these JMS messages (the two outbound and the one inbound), if at // here a:
rollback exception is thrown,
something (or nothing) occurs, resulting in committing the transaction.
I'm particularly interested in how the convertSendAndReceive(...) is managed, because obviously a response cannot be received until the send is committed - is the send of Baz handled in a different transaction to the send of Bar?
While I would expect the messaging implementation to not make a huge difference, I'm mostly interested in ActiveMQ.
I am using Apache Camel to send messages to my Java service. I have kept transacted=true on consumer route. I also need to send e-mail on successfully processing of JMS messages.
I am using below code to register synchronization and send e-mail only after transaction is committed.
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
#Override
public void afterCommit(){
sendMail(mailBody);
}
});
Problem: incoming transaction from Camel is not synchronized and I am getting
java.lang.IllegalStateException: Transaction synchronization is not active
I tried calling transactionsynchronizationmanager.initsynchronization() - I am not getting any exception but afterCommit() method is never called.
transactionsynchronizationmanager.initsynchronization();
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
#Override
public void afterCommit(){
sendMail(mailBody); //never called
}
});
Same code is working when request is received via spring mvc controller (through Spring Transaction).
You likely need to turn on transacted on the route to enable spring transaction. The option transacted=true on the JMS endpoint is NOT spring-transaction, but its only for JMS acknowledge mode to be set as transacted. They are not the same.
So in your Camel route, setup spring transaction as well, eg
from jms
transacted
See more details in the Camel docs: http://camel.apache.org/transactional-client.html or even better if you have a copy of the Camel in Action book (1st or 2nd ed) then it has a fully chapter devoted to transactions.
It´s more of a conceptual question: I currently have a working activemq queue which is consumed by a Java Spring application. Now I want the queue not to permanently delete the messages until the Java app tells it the message has been correctly saved in DB. After reading documentation I get I have to do it transactional and usa the commit() / rollback() methods. Correct me if I'm wrong here.
My problem comes with every example I find over the internet telling me to configure the app to work this or that way, but my nose tells me I should instead be setting up the queue itself to work the way I want. And I can't find the way to do it.
Otherwise, is the queue just working in different ways depending on how the consumer application is configured to work? What am I getting wrong?
Thanks in advance
The queue it self is not aware of any transactional system but you can pass the 1st parameter boolean to true to create a transactional session but i propose the INDIVIDUAL_ACKNOWLEDGE when creating a session because you can manage messages one by one. Can be set on spring jms DefaultMessageListenerContainer .
ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE
And calling this method to ack a message, unless the method is not called the message is considered as dispatched but not ack.
ActiveMQTextMessage.acknowledge();
UPDATE:
ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE can be used like this :
onMessage(ActiveMQTextMessage message)
try {
do some stuff in the database
jdbc.commit(); (unless auto-commit is enabled on the JDBC)
message.acknowledge();
}
catch (Exception e) {
}
There are 2 kinds of transaction support in ActiveMQ.
JMS transactions - the commit() / rollback() methods on a Session (which is like doing commit() / rollback() on a JDBC connection)
XA Transactions - where the XASession acts as an XAResource by communicating with the Message Broker, rather like a JDBC Connection takes place in an XA transaction by communicating with the database.
http://activemq.apache.org/how-do-transactions-work.html
Should I use XA transactions (two phase commit?)
A common use of JMS is to consume messages from a queue or topic, process them using a database or EJB, then acknowledge / commit the message.
If you are using more than one resource; e.g. reading a JMS message and writing to a database, you really should use XA - its purpose is to provide atomic transactions for multiple transactional resources. For example there is a small window from when you complete updating the database and your changes are committed up to the point at which you commit/acknowledge the message; if there is a network/hardware/process failure inside that window, the message will be redelivered and you may end up processing duplicates.
http://activemq.apache.org/should-i-use-xa.html
I have JMS queue implementation in my project in which I am sending 100's of messages in one transaction but performing some DB operations before putting it in queue. i.e
//SuedoCode
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendMsg(List orders )
{
for(Order order : orders)
{
order.setStatus("SENT");
sendToQueue(order);
}
}
But this transaction is still not committed and receiver picks up the orders before sender's transaction is committed. Now receiver process the messages and change the status again then commits but after that senders transaction commits and it overrides the status which should not happen.
Thus to solve this problem I created a new class (For spring proxy) which has method to change the status of order and this method is in REQUIRES_NEW transaction so the status has changed but If any error occur while sending the message to queue then again status needs to changed (because the previous transaction has already committed).
Please suggest me If this approach is correct or something better can be done.
Thanks in advance
The messages queued need to be part of the same transaction, so that everything gets committed at once.
Lacking that, another solution is to stored updated orders in a list and call sendToQueue outside this method as soon as your sure the transaction has been committed and database has been updated.