У меня есть приложение Spring Boot с @JmsListener
, которое получает сообщение из очереди, сохраняет его в базе данных и отправляет в другую очередь.
Я хотел иметь минимальную транзакционную гарантию, поэтому 1-Phase-commit работает для меня. После долгих чтений я обнаружил, что могу использовать ChainedTransactionManager для координации ресурсов DataSource и JMS:
@Configuration
public class TransactionConfiguration {
@Bean
public ChainedTransactionManager transactionManager(JpaTransactionManager jpaTm, JmsTransactionManager jmsTm) {
return new ChainedTransactionManager(jmsTm, jpaTm);
}
@Bean
public JpaTransactionManager jpaTransactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean
public JmsTransactionManager jmsTransactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
}
Прослушиватель очереди:
@Transactional(transactionManager = "transactionManager")
@JmsListener(...)
public void process(@Payload String message) {
//Write to db
//Send to output queue
}
START MESSAGING TX
START DB TX
READ MESSAGE
WRITE DB
SEND MESSAGE
COMMIT DB TX
COMMIT MESSAGING TX
- Если фиксация db завершится неудачно, сообщение будет повторно обработано повторно
- Если фиксация db завершится успешно, но фиксация обмена сообщениями завершится неудачно, сообщение будет обработано повторно. Это не проблема, поскольку я могу гарантировать идемпотентность операции записи в БД
Теперь я сомневаюсь, давайте предположим, что я не настроил ChainedTransactionManager
, и слушатель был таким (нет *)1020 *):
@JmsListener(...)
public void process(@Payload String message) {
//Write to db
//Send to output queue
}
Разве это не ведет себя так же, как в другом примере, несмотря на отсутствие координации коммитов? (Я проверил, что для исключений SQL сообщение доставляется)
RECEIVE MESSAGE
WRITE DB + COMMIT
SEND MESSAGE + COMMIT
- В случае неудачной фиксации БД сообщение будет обработано повторно
- В случае успеха и операции отправки сообщенияне удалось его повторно обработать.
Так действительно ли необходимо ChainedTransactionManager
в этом случае?
UPDATE : отладка автоконфигурации Spring Boot (JmsAnnotationDrivenConfiguration
) ...
@Bean
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
... DefaultJmsListenerContainerFactoryConfigurer
настраивает фабрику с factory.setSessionTransacted(true);
, поскольку не определено JtaTransactionManager
:
if (this.transactionManager != null) {
factory.setTransactionManager(this.transactionManager);
}
else {
factory.setSessionTransacted(true);
}
С setSessionTransacted (true), в соответствии с Spring doc я получу нужное мне поведение отката и повторной доставки сообщения для БД (или любых) исключений:
Локальные транзакции ресурсов могут быть просто активированы черезфлаг sessionTransacted в определении контейнера слушателя. Каждый вызов прослушивателя сообщений будет затем работать в активной транзакции JMS, при этом прием сообщений будет отменен в случае сбоя при выполнении прослушивателя. Отправка ответного сообщения (через SessionAwareMessageListener) будет частью той же локальной транзакции, но любые другие операции с ресурсами (например, доступ к базе данных) будут работать независимо. Для этого обычно требуется обнаружение дубликатов сообщений в реализации прослушивателя, что относится к случаю, когда обработка базы данных завершилась, но обработка сообщения не удалось зафиксировать.
Это объясняет, что я получаю ожидаемое поведение без необходимости конфигурироватьChainedTransactionManager
.
После всего этого, не могли бы вы сказать мне, имеет ли смысл (добавляет некоторую гарантию, что мне не хватает) использовать ChainedTransactionManager
в этом случае?