如何实现仅在JPA事务提交后发送JMS消息?
首先咱们拆解下你遇到的问题:你期望被@Transactional包裹的方法执行完(包括打印"This should run first")之后,JMS监听器才收到消息,但现在消息是立即发送的,导致监听器的打印先出现——这是因为默认情况下JmsTemplate的send操作不会绑定到Spring事务,会直接立即发送消息到MQ,和当前的事务提交状态完全无关。
问题根源
你的TestService方法虽然加了@Transactional,但如果JmsTemplate没有配置事务同步,convertAndSend会直接提交JMS会话,把消息立刻投递到ActiveMQ。所以监听器会马上消费消息,而Thread.sleep和后续的打印反而在后面执行,顺序自然就反过来了。
解决方案:让JmsTemplate绑定到Spring事务
你不需要XA事务(除非你需要JMS消息发送和Hibernate的DB操作完全原子化,要么都成功要么都失败),只需要配置JmsTemplate支持事务同步,让消息延迟到事务提交后再发送。以下是两种可行的配置方式:
方式1:给JmsTemplate开启会话事务
直接给JmsTemplate设置sessionTransacted = true,这样当方法在@Transactional上下文里执行时,JMS会话会绑定到当前Spring事务,消息发送会延迟到事务提交后:
@Bean public JmsTemplate jmsTemplate(ConnectionFactory activeMQConnectionFactory) { JmsTemplate jmsTemplate = new JmsTemplate(activeMQConnectionFactory); jmsTemplate.setSessionTransacted(true); // 关键配置:开启会话事务,绑定到Spring事务 return jmsTemplate; }
方式2:用TransactionAwareConnectionFactoryProxy包装连接工厂
这种方式会让连接工厂感知Spring事务,JmsTemplate使用这个代理后的连接工厂时,自动把消息发送延迟到事务提交:
@Bean public ConnectionFactory transactionAwareConnectionFactory(ActiveMQConnectionFactory activeMQConnectionFactory) { return new TransactionAwareConnectionFactoryProxy(activeMQConnectionFactory); } @Bean public JmsTemplate jmsTemplate(ConnectionFactory transactionAwareConnectionFactory) { return new JmsTemplate(transactionAwareConnectionFactory); }
验证效果
配置完成后,你的TestService.test()方法执行流程会变成:
- 调用
jmsTemplate.convertAndSend,但消息不会立刻发送到MQ,而是被暂存到事务同步资源中 - 执行
Thread.sleep(1000L) - 打印"This should run first"
- 方法执行完毕,Spring提交当前事务
- 事务提交后,JmsTemplate才真正把消息发送到ActiveMQ
- 监听器
Listener.onMessage收到消息,打印"This should run last."
这样就能完美实现你期望的执行顺序了。
关于XA事务的补充
如果你的方法中同时有JMS消息发送和Hibernate的数据库操作,并且要求这两个操作必须原子性(比如DB操作失败的话,消息也不能发送),那才需要配置XA事务(使用XA版的ActiveMQ连接工厂和JtaTransactionManager)。但从你的问题描述来看,你只是需要消息在事务提交后发送,普通的事务同步配置就足够了,完全不需要XA。
内容的提问来源于stack exchange,提问作者Zecrates

