Spring JmsTransactionManager获取TransactionStatus为null问题排查
问题描述
使用Spring JmsTransactionManager时,偶尔出现TransactionStatus为null的情况,添加重试逻辑也未改善。调用rollback时触发NullPointerException,异常信息如下:
Could not create JMS transaction
java.lang.NullPointerException: Cannot invoke "org.springframework.transaction.TransactionStatus.isCompleted()" because "status" is null
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:859)
同时存在疑问:该问题是否与CachingConnectionFactory配合JmsTransactionManager使用有关?或是因为底层MQ主机偶尔不可用?
异常分析
从异常栈和代码逻辑来看,核心问题是事务创建失败时status变量保持null,直接调用rollback(status)触发空指针。结合配置与代码,可从以下几点定位原因:
原因定位
1. 代码逻辑漏洞
原publishMessages方法存在明显问题:
getTransaction可能因MQ连接失败等原因抛出异常,此时status未被赋值,仍为nullcommit语句放在try块外部,无论消息发送是否成功都会执行,不符合事务规范- catch块直接调用
rollback(status),未判断status是否有效
2. CachingConnectionFactory缓存连接失效
CachingConnectionFactory会缓存连接和会话,当底层MQ连接断开后,缓存的连接可能处于无效状态:
- 尝试使用无效连接创建事务时,
getTransaction会失败,抛出异常导致status为null - 未配置连接校验机制,无法及时剔除无效缓存连接
3. 底层MQ主机不可用或网络波动
当MQ主机偶尔不可用、网络波动时:
- 创建JMS连接/会话失败,导致
getTransaction无法生成有效TransactionStatus,抛出异常后status保持null - 原ConnectionFactory创建逻辑中,捕获异常后仍返回未正确初始化的MQConnectionFactory,后续使用会持续触发问题
解决方案
1. 修复代码逻辑,避免空指针
调整事务处理逻辑,确保仅在status有效时执行commit/rollback,同时规范事务边界:
public void publishMessages(String payload, MQQueue destinationQueue) throws Exception { TransactionStatus status = null; try { status = jmsTransactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW)); jmsTemplate.send(destinationQueue, session -> { TextMessage txtMessage = session.createTextMessage(); txtMessage.setText(payload); return txtMessage; }); // 仅当status有效时提交事务 if (status != null) { jmsTransactionManager.commit(status); } } catch (Exception e) { log.error("消息发送失败,准备回滚事务", e); // 回滚前先判断status是否有效 if (status != null) { try { jmsTransactionManager.rollback(status); } catch (Exception rollbackException) { log.error("事务回滚失败", rollbackException); } } // 抛出异常,避免吞掉错误信息 throw e; } }
2. 优化CachingConnectionFactory配置
添加连接校验,确保缓存的连接有效,同时调整缓存策略:
@Bean(name = "gatewayCachingConnectionFactory") public CachingConnectionFactory gatewayCachingConnectionFactory() { CachingConnectionFactory factory = new CachingConnectionFactory(); factory.setSessionCacheSize(50); factory.setTargetConnectionFactory(gatewayConnectionFactory()); // 借出连接时校验有效性,自动剔除无效连接 factory.setTestOnBorrow(true); // 设置连接过期时间,清理长时间未使用的连接 factory.setCacheConsumers(false); // 根据实际消费场景调整,若使用消费者可开启 return factory; }
3. 修复ConnectionFactory创建逻辑
确保ConnectionFactory初始化失败时不返回无效对象:
@Qualifier("gatewayConnectionFactory") public MQConnectionFactory gatewayConnectionFactory() throws Exception { MQConnectionFactory mqConnectionFactory = new MQConnectionFactory(); try { mqConnectionFactory.setHostName(hostname); mqConnectionFactory.setPort(port); mqConnectionFactory.setChannel(channelname); mqConnectionFactory.setTransportType(1); // 提前校验连接有效性,避免返回无效配置 mqConnectionFactory.createConnection(); } catch (Exception e) { log.error("连接MQ网关失败", e); // 抛出异常,禁止返回未初始化完成的ConnectionFactory throw e; } return mqConnectionFactory; }
4. 改用Spring声明式事务(推荐)
使用@Transactional注解代替编程式事务,Spring会自动处理事务的创建、提交和回滚,避免手动管理的漏洞:
@Component public class MessageDeliver { @Autowired @Qualifier("jmsTemplate") private JmsTemplate jmsTemplate; // 指定事务管理器和传播特性,异常自动回滚 @Transactional(transactionManager = "jmsTransactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void publishMessages(String payload, MQQueue destinationQueue) throws Exception { jmsTemplate.send(destinationQueue, session -> { TextMessage txtMessage = session.createTextMessage(); txtMessage.setText(payload); return txtMessage; }); } }
5. 添加重试机制应对临时异常
针对MQ连接异常,使用Spring Retry添加重试逻辑,处理临时网络波动:
// 配置RetryTemplate @Bean public RetryTemplate retryTemplate() { SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(3); // 重试3次 FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); backOffPolicy.setBackOffPeriod(1000); // 每次重试间隔1秒 RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(retryPolicy); retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } // 在MessageDeliver中使用RetryTemplate包裹发送逻辑 @Autowired private RetryTemplate retryTemplate; public void publishMessages(String payload, MQQueue destinationQueue) throws Exception { retryTemplate.execute(context -> { publishWithTransaction(payload, destinationQueue); return null; }); } @Transactional(transactionManager = "jmsTransactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) private void publishWithTransaction(String payload, MQQueue destinationQueue) throws Exception { jmsTemplate.send(destinationQueue, session -> { TextMessage txtMessage = session.createTextMessage(); txtMessage.setText(payload); return txtMessage; }); }
内容的提问来源于stack exchange,提问作者VKR

