RocketMQ事务消息

恕我直言,网上搜到的关于rocketMQ事务消息的实例都是屁话(也有可能是我菜),毫无实战可言

这里不做事务消息的原理和用途介绍(原理网上一大堆),这里解决 RocketMQLocalTransactionListener 一个 project 里只能有一个的问题。

以下内容全部以 springboot-rocketmq 依赖为基础

xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>

好像因为 TransactionMQProducer 在执行事务时不能被共享, 一个项目里如果包含多个 @RocketMQTransactionListener, 启动将会报错。

那我们的项目里如果不止一个业务场景需要发送事务消息,那怎么办呢?

1.仿照 RocketMQLocalTransactionListener 自定义一个相同的接口 TransactionMessageCommonListener

java 复制代码
public interface TransactionMessageCommonListener<T> {

    RocketMQLocalTransactionState executeLocalTransaction(MessageHeaders headers, T message, Object args);

    RocketMQLocalTransactionState checkLocalTransaction(MessageHeaders headers, T message);

}

2.定义一个实现了 RocketMQLocalTransactionListener 接口的默认实现类

java 复制代码
@Component
@RocketMQTransactionListener
public class DefaultRocketMQTransactionMessageImpl implements RocketMQLocalTransactionListener {

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String listenerName = (String) msg.getHeaders().get("listener");
        if (null == listenerName) {
            throw new RuntimeException("not params transactionMessageListener");
        }
        RocketMQLocalTransactionState state;
        try {
            TransactionBody transactionBody = transactionBodyMap.get(listenerName);
            if (null == transactionBody) {
                throw new RuntimeException("not match condition transactionMessageListener");
            }
            state = transactionBody.getListener().executeLocalTransaction(msg.getHeaders(), MessageUtil.doConvertMessage(transactionBody.getType(), msg), arg);
        } catch (Exception e) {
            log.error("rocket transaction message executeLocal error:{}", e.getMessage());
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return state;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String listenerName = (String) msg.getHeaders().get("listener");
        if (null == listenerName) {
            throw new RuntimeException("not params transactionMessageListener");
        }
        RocketMQLocalTransactionState state;
        try {
            TransactionBody transactionBody = transactionBodyMap.get(listenerName);
            if (null == transactionBody) {
                throw new RuntimeException("not match condition transactionMessageListener");
            }
            state = transactionBody.getListener().checkLocalTransaction(msg.getHeaders(), MessageUtil.doConvertMessage(transactionBody.getType(), msg));
        } catch (Exception e) {
            log.error("rocket transaction message checkLocal error:", e);
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return state;
    }
}

3.定义一个包装类 TransactionBody,方便后续处理。再在上一步的默认实现类里对我们自定义的 TransactionMessageCommonListener 接口做一个所有实现类的缓存

java 复制代码
public class TransactionBody {

    private TransactionMessageCommonListener listener;

    private Type type;

    public TransactionMessageCommonListener getListener() {
        return listener;
    }

    public void setListener(TransactionMessageCommonListener listener) {
        this.listener = listener;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public TransactionBody (){};

    public TransactionBody(TransactionMessageCommonListener listener) {
        this.listener = listener;
        this.type = ((ParameterizedType) listener.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
    }
}
java 复制代码
private Map<String, TransactionBody> transactionBodyMap;

//该方法是在spring启动时完成调用的
//如果你的 TransactionMessageCommonListener 实现类会加入spring bean管理,TransactionBody::new 可以替换成 SpringBeanUtil.getBean(item.getClass)
@Autowired(required = false)
public void setTransactionMessageListeners(List<TransactionMessageCommonListener<?>> transactionMessageListeners) {
	if (!transactionMessageListeners.isEmpty()) {
		transactionBodyMap = transactionMessageListeners.stream()
				.collect(Collectors.toMap(item -> item.getClass().getSimpleName(), TransactionBody::new));
	}
}

4.定义发送消息的方法

java 复制代码
//msgBody是我自己封装的消息包装类,你可以根据你自己需要封装一个或者不封装
//msgBody.getContentJson()是你发送的业务数据,你可以换成你自己的
@Override
public <T> void sendTransaction(MsgBody msgBody, Object args, Class<? extends TransactionMessageCommonListener<T>> transactionMessageListener) {
    rocketMQTemplate.sendMessageInTransaction(msgBody.getTopic() + ":" + msgBody.getTags(), MessageBuilder.withPayload(msgBody.getContentJson()).setHeader("listener", transactionMessageListener.getSimpleName()).build(), args);
}

到这里对事务消息的改造就完成了,接下来是使用

1.发送一个事务消息,并指定本地事务提交的实现类是哪一个

java 复制代码
Order order = new Order("A123", "测试订单", LocalDateTime.now());
rocketService.sendTransaction(
        new MsgBody()
                .setContent(JSONObject.toJsonString(order))
                .setTopic("TEST_TX_TOPIC")
                .setTags("TEST_TX_TAG"),
        order,
        CreateOrderTransactionMessageListener.class
);

2.定义 CreateOrderTransactionMessageListener 实现刚才我们定义好的 TransactionMessageCommonListener 接口

java 复制代码
public class CreateOrderTransactionMessageListener implements TransactionMessageCommonListener<Order> {
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(MessageHeaders headers, Order message, Object args) {
        
        //执行业务代码(paySuccessCrateOrder 方法包含了 @Transactional)
        boolean isSuccess = orderService.paySuccessCrateOrder((Order) args);
        
        return isSuccess ?  RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.UNKNOWN;
        
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(MessageHeaders headers, Order message) {
        
        //查询刚才的数据是否成功落地,成功就提交消息,没有就回滚消息
        Order order = orderService.queryOrderIsExist(message.getOrderId());
        
        return null != order ? RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
        
    }
}

以后项目里有其他业务场景需要发送事务消息,只需要实现一个 TransactionMessageCommonListener 接口,并在发送消息时指定这个实现类就好了。

该代码是一两年前写的,有一些地方可能设计并不合理或需要优化,请根据自己的实际需要进行修改。copy代码只能保证正常运行。

相关推荐
阳光是sunny3 小时前
LangGraph中的Reducer是什么
前端·人工智能·后端
灯澜忆梦3 小时前
GO_并发编程---定时器
开发语言·后端·golang
阳光是sunny3 小时前
从链到图:LangGraph 入门基础全解析
前端·人工智能·后端
皮皮林5514 小时前
开源实力派,给本地 AI 装上深度调研能力,一张 3090 跑到 95.7 分!
后端
努力的小雨4 小时前
把 Windows 桌面图标做成一个会自己运转的小世界
后端
武子康5 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
腾渊信息科技公司5 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
IT_陈寒7 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
段一凡-华北理工大学7 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
码事漫谈7 小时前
1999年的电商前夜和2026年的AI前夜 历史押韵但从不重复
后端