Reputation: 1605
I am trying to understand few things here. My requirement was I want to store record in db and want to send message to queue and then lets say in same method if it throws some exception I don't want to send message and don't want to commit db transaction. Now I thought of using spring transaction but since two different resources, thought of using JTA using some atomikos to synchronize resources - but again I read RMQ do not support 2PC or XA etc. Anyway I went ahead and tried first without adding atomikos, all I did is made sure that my channel transacted and @Transaction annotation took care, see below sample code - I didn't added anything special in pom.
Now my question is how is this working, how is this different from 2PC - and what can go wrong with approach and what situations can mess up eventual consistency using this method. And surprisingly why I didn't have to use third party jta. If all is good with this - this seems to me eventual consistency guarantee when we use rmq and db using spring goodies! for microservices :)
If this is not good solution what are alternatives - I would like to avoid worker process etc if possible for eventual consistency.
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
@GetMapping
@Transactional
public void sampleEndpoint(@RequestParam boolean throwException){
Customer a=new Customer();
a.setCustomerName("XYZ");
customerRepository.save(a);
rabbitTemplate.convertAndSend("txtest","Test");
if(throwException)
throw new RuntimeException();
}
I used postgres dependency for above example using spring boot 1.5.7
Upvotes: 4
Views: 7779
Reputation: 1967
Regarding the "How is it working" question, this quote from spring-amqp documentation clarifies:
If there is already a transaction in progress when the framework is sending or receiving a message, and the channelTransacted flag is true, then the commit or rollback of the messaging transaction will be deferred until the end of the current transaction. If the channelTransacted flag is false, then no transaction semantics apply to the messaging operation (it is auto-acked).
My understanding is that, for your use case, you do not even need to configure a ChainedTransactionManager in order to implement Best Effort 1PC. @Transactional will be enough and the rabbit tx will commit right after the DB tx.
Upvotes: 2
Reputation: 174484
I suggest you read Dave Syer's article: Distributed transactions in Spring, with and without XA.
You need to start the Rabbit transaction before the database transaction so the rabbit transaction is synchronized with the DB transaction and commits very soon after the DB tx and rolls back if the DB tx rolls back.
There is a small possibility that the DB tx commits successfully but the Rabbit tx rolls back. This is called "Best Effort 1PC" in the article. You need to deal with the small possibility of duplicate messages.
You don't show all your configuration but it appears your Rabbit tx will commit before the DB, which is probably not what you want.
Upvotes: 7