一、使用Atomikos分布式事务:
说明:基于XA协议的两阶段提交:prepare阶段、commit阶段。
1.添加mysql、druid、atomikos等依赖包:
<dependencies>
<!-- 添加mysql驱动相关依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- 添加druid连接池相关依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- 添加mybatis相关依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- 添加atomikos依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
</dependencies>
2.mysql0主机ds0数据库插入操作(IP:141):
(1)实体类Order0.java:
package com.yyh.mybatis.domain;
public class Order0 {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)Dao操作类OrderMapper0.java:
package com.yyh.mybatis.dao0;
@Mapper //标记为Dao操作类
public interface OrderMapper0 {
void insert(Order0 order);
}
(3)映射文件resources/ds0/OrderMapper0.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyh.mybatis.dao0.OrderMapper0"> <!-- 配置对象关系映射 -->
<insert id="insert" parameterType="com.yyh.mybatis.domain.Order0"> <!-- id为方法名,parameterType传参类型-->
insert into t_order(id,name) values(#{id},#{name}) <!-- #{}类似?占位符,将对象属性值存入对应的同名列中 -->
</insert>
</mapper>
(4)ds0数据库的Atomikos配置类:
@Configuration
@MapperScan(value = "com.yyh.mybatis.dao0", sqlSessionFactoryRef = "sqlSessionFactoryBean0")
public class DS0Config {
@Bean("ds0")
public DataSource ds0() { //连接ds0数据库
DruidXADataSource ds = new DruidXADataSource(); //使用Druid数据源
ds.setUrl("jdbc:mysql://192.168.233.141:3306/ds0");
ds.setUsername("root");
ds.setPassword("root123456");
AtomikosDataSourceBean adsBean = new AtomikosDataSourceBean();
adsBean.setXaDataSource(ds); //将Druid数据源交由Atomikos管理
adsBean.setUniqueResourceName("DS0Config");
return adsBean;
}
@Bean("sqlSessionFactoryBean0")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds0") DataSource dataSource) throws IOException {
SqlSessionFactoryBean ssfBean = new SqlSessionFactoryBean();
ssfBean.setDataSource(dataSource);
ResourcePatternResolver rpResolver = new PathMatchingResourcePatternResolver();
ssfBean.setMapperLocations(rpResolver.getResources("classpath:ds0/*.xml"));
ssfBean.setTypeAliasesPackage("com.yyh.mybatis.dao0");
return ssfBean;
}
@Bean("tm") //创建事务管理器
public JtaTransactionManager jtaTransactionManager() {
UserTransaction ut = new UserTransactionImp();
UserTransactionManager utm = new UserTransactionManager();
return new JtaTransactionManager(ut, utm);
}
}
3.mysql1主机ds1数据库插入操作(IP:142):
(1)实体类Order1.java:
package com.yyh.mybatis.domain;
public class Order1 {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)Dao操作类OrderMapper1.java:
package com.yyh.mybatis.dao1;
@Mapper //标记为Dao操作类
public interface OrderMapper1 {
void insert(Order1 order);
}
(3)映射文件resources/ds1/OrderMapper1.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyh.mybatis.dao1.OrderMapper1"> <!-- 配置对象关系映射 -->
<insert id="insert" parameterType="com.yyh.mybatis.domain.Order1"> <!-- id为方法名,parameterType传参类型-->
insert into t_order(id,name) values(#{id},#{name}) <!-- id类型为bigint,使用雪花算法自增,#{}类似?占位符,将对象属性值存入对应的同名列中 -->
</insert>
</mapper>
(4)ds1数据库的Atomikos配置类:
@Configuration
@MapperScan(value = "com.yyh.mybatis.dao1", sqlSessionFactoryRef = "sqlSessionFactoryBean1")
public class DS1Config {
@Bean("ds1")
public DataSource ds1() { //连接ds1数据库
DruidXADataSource ds = new DruidXADataSource(); //使用Druid数据源
ds.setUrl("jdbc:mysql://192.168.233.142:3306/ds1");
ds.setUsername("root");
ds.setPassword("root123456");
AtomikosDataSourceBean adsBean = new AtomikosDataSourceBean();
adsBean.setXaDataSource(ds); //将Druid数据源交由Atomikos管理
adsBean.setUniqueResourceName("DS1Config");
return adsBean;
}
@Bean("sqlSessionFactoryBean1")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds1") DataSource dataSource) throws IOException {
SqlSessionFactoryBean ssfBean = new SqlSessionFactoryBean();
ssfBean.setDataSource(dataSource);
ResourcePatternResolver rpResolver = new PathMatchingResourcePatternResolver();
ssfBean.setMapperLocations(rpResolver.getResources("classpath:ds1/*.xml"));
ssfBean.setTypeAliasesPackage("com.yyh.mybatis.dao1");
return ssfBean;
}
}
4.使用@Transactional注解打开分布式事务:
@Service
public class OrderService {
@Resource
private OrderMapper0 orderMapper0; //操作mysql0主机ds0数据库
@Resource
private OrderMapper1 orderMapper1; //操作mysql1主机ds1数据库
@Transactional(transactionManager = "tm") //引入DS0Config类中创建的tm,方法内所有业务操作均在事务内,出错时整体回滚
public void insert() {
//将数据插入到mysql0主机ds0数据库
Order0 order0 = new Order0();
...
orderMapper0.insert(order0);
//将数据插入到mysql1主机ds1数据库
Order1 order1 = new Order1();
...
orderMapper1.insert(order1);
}
}
二、MyCat/Sharding-Jdbc分布式事务(默认已开启,加@Transactional使用):
1.使用MyCat分布式事务:
(1)打开分布式事务(cd /usr/local/mycat/conf,vi server.xml):
<mycat:server ...>
<system>
<property name="handleDistributedTransactions">0</property> <!-- (默认开启)0为不过滤分布式事务,1为过滤分布式事务(只操作全局表不过滤)(关闭),2为不过滤只记录分布式事务日志 -->
</system>
</mycat:server>
(2)使用@Transactional注解开启事务:
@Service
public class OrderService {
...
@Transactional(rollbackFor = Exception.class) //开启事务,所有异常回滚
public void insert() {
...
}
}
2.使用Sharding-Jdbc分布式事务:
@Service
public class OrderService {
...
@Transactional(rollbackFor = Exception.class) //开启事务,所有异常回滚
public void insert() {
...
}
}
三、其他方式处理分布式事务:
1.使用补偿机制处理分布式事务(catch中处理异常时的逻辑):
@Transactional(transactionManager = "mysql0的transactionManager", rollbackFor = Exception.class) //mysql0异常时使用事务自动回滚
public void pay(){
//对mysql0的增删改业务,如金额表扣钱操作
...
try {
//对mysql1的增删改业务,比如订单更新为已支付状态
...
} catch (Exception exception) {
//对mysql1的业务进行回滚的代码,比如将订单支付状态重置为未支付,失败时计次数,重新执行,直到成功或超过最大执行次数时退出
...
throw exception; //抛出异常,让mysql0的业务也使用事务回滚
}
}
2.使用本地消息表的方式处理分布式事务(此处以订单支付为例):
说明:允许一段时间内的不一致,最终会一致。
业务流程:
1>在一个事务内,金额表扣钱,同时插入支付信息到消息表,金额表与消息表在同一个数据库;
2>开启定时任务,查询消息表中未支付的订单列表,并请求订单更新接口,将支付结果更新到订单表。
Mysql0主机ds0数据库各表结构:
消息表t_msg_table:id(主键)、order_id(订单id)、pay_status(支付状态)、retry_count(标记重试次数)、user_no(用户id)、create_time(创建时间)、update_time(更新时间)...
金额表t_amount:id(主键)、user_no(用户id)、amount(金额,decimal类型)、create_time(创建时间)、update_time(更新时间)...
Mysql1主机ds1数据库-订单表t_order结构:
id(主键,订单id)、amount(金额,decimal类型)、pay_status(支付状态)、user_no(用户id)、create_time(创建时间)、update_time(更新时间)...
(1)金额表-扣钱+消息表-插入支付信息:
AmountService.java(金额表与消息表操作业务类):
@Service
public class AmountService { //操作金额表与消息表
@Resource
private AmountMapper amountMapper; //操作mysql0主机ds0数据库
@Resource
private MsgTableMapper msgTableMapper; //操作mysql0主机ds0数据库
@Transactional(transactionManager = "ds0_tm") //引入DS0Config类中创建的tm0
public int subtract(int userNo, int orderId, BigDecimal amount) {
//1.金额表-扣钱
Amount a = amountMapper.query(userNo);
if (a == null) return Constants.AMOUNT_ERROR; //此用户没有金额记录
if (a.getAmount().compareTo(amount) < 0) return Constants.AMOUNT_NO_MONEY; //余额不足
a.setAmount(a.getAmount().subtract(amount)); //a.getAmount() - amount
amountMapper.update(a);
//2.消息表-记录支付信息,等待支付成功时更新订单状态用
MsgTable msg = new MsgTable();
msg.setOrderId(orderId);
msg.setPayStatus(Constants.ORDER_NO_PAY); //订单表状态还是未支付
msg.setRetryCount(0); //重试次数初始为0
msg.setUserNo(userNo);
msg.setCreateTime(new Date(System.currentTimeMillis()));
msg.setUpdateTime(msg.getCreateTime());
msgTableMapper.insert(msg);
return Constants.AMOUNT_SUCCESS;
}
}
PayController.java(支付相关接口):
@Controller
@RequestMapping("/pay")
public class PayController { //支付相关接口
@Autowired
private AmountService amountService;
@RequestMapping("/pay") //支付接口,此接口供浏览器等客户端调用
@ResponseBody
public String pay(int userNo, int orderId, BigDecimal amount) {//省略其他参数
//校验签名信息
//...省略
int status = amountService.subtract(userNo, orderId, amount);
return String.valueOf(status);
}
}
(2)开启定时任务,查询消息表中未支付的订单列表,并请求订单更新接口,将支付结果更新到订单表:
MyApplication.java:
@SpringBootApplication
@EnableScheduling //开启定时任务
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
OrderTimer.java(定时任务业务类):
@Service
public class OrderTimer {//定时任务业务类
private final int MAX_COUNT = 10;
@Resource
private MsgTableMapper msgTableMapper;
@Scheduled(cron = "0/10 * * * * ?")
public void updateOrderPayStatus() throws IOException { //定时执行,更新订单支付状态
List<MsgTable> list = msgTableMapper.queryAll(Constants.ORDER_NO_PAY); //查询未支付的记录
if (list == null || list.isEmpty()) return;
for (MsgTable item : list) {
requestOrderServer(item);
}
}
private void requestOrderServer(MsgTable item) throws IOException {//远程HTTP方式调用更新订单支付状态接口
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpPost request = new HttpPost("http://localhost:9998/order/update_pay_status");
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("orderId", String.valueOf(item.getOrderId())));
request.setEntity(new UrlEncodedFormEntity(params));
CloseableHttpResponse response = client.execute(request);
String result = EntityUtils.toString(response.getEntity());
if (result == null || result.isEmpty()) return;
if (Integer.parseInt(result) == Constants.ORDER_PAY_SUCCESS) {
item.setPayStatus(Constants.ORDER_PAY_SUCCESS);
item.setUpdateTime(new Date(System.currentTimeMillis()));
msgTableMapper.update(item);
return;
}
if (item.getRetryCount() >= MAX_COUNT) {
item.setPayStatus(Constants.ORDER_ERROR);
item.setUpdateTime(new Date(System.currentTimeMillis()));
msgTableMapper.update(item);
return;
}
item.setRetryCount(item.getRetryCount() + 1);
msgTableMapper.update(item);
}
}
OrderController.java(订单支付更新接口):
@Controller
@RequestMapping("/order")
public class OrderController { //订单相关接口
@Autowired
private OrderService orderService;
@RequestMapping("/update_pay_status") //更新订单支付状态,此接口供定时任务远程HTTP调用
@ResponseBody
public String updatePayStatus(int orderId) {
int payStatus = orderService.updatePayStatus(orderId);
return String.valueOf(payStatus);
}
}
(3)其他基础配置:
<1>pom.xml依赖:
<dependencies>
<!-- 添加mysql驱动相关依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- 添加mybatis相关依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- 添加httpclient相关依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
<2>数据源:
DS0Config.java:
@Configuration
@MapperScan(value = "com.yyh.mybatis.dao0", sqlSessionFactoryRef = "sqlSessionFactoryBean0")
public class DS0Config {
@Bean("ds0")
public DataSource ds0() { //连接ds0数据库
DruidDataSource ds = new DruidDataSource(); //使用Druid数据源
ds.setUrl("jdbc:mysql://192.168.233.141:3306/ds0");
ds.setUsername("root");
ds.setPassword("root123456");
return ds;
}
@Bean("sqlSessionFactoryBean0")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds0") DataSource dataSource) throws IOException {
SqlSessionFactoryBean ssfBean = new SqlSessionFactoryBean();
ssfBean.setDataSource(dataSource);
ResourcePatternResolver rpResolver = new PathMatchingResourcePatternResolver();
ssfBean.setMapperLocations(rpResolver.getResources("classpath:ds0/*.xml"));
ssfBean.setTypeAliasesPackage("com.yyh.mybatis.dao0");
return ssfBean;
}
@Bean("ds0_tm") //创建事务管理器
public PlatformTransactionManager transactionManager(@Qualifier("ds0") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
DS1Config.java:
@Configuration
@MapperScan(value = "com.yyh.mybatis.dao1", sqlSessionFactoryRef = "sqlSessionFactoryBean1")
public class DS1Config {
@Bean("ds1")
public DataSource ds1() { //连接ds1数据库
DruidDataSource ds = new DruidDataSource(); //使用Druid数据源
ds.setUrl("jdbc:mysql://192.168.233.142:3306/ds1");
ds.setUsername("root");
ds.setPassword("root123456");
return ds;
}
@Bean("sqlSessionFactoryBean1")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds1") DataSource dataSource) throws IOException {
SqlSessionFactoryBean ssfBean = new SqlSessionFactoryBean();
ssfBean.setDataSource(dataSource);
ResourcePatternResolver rpResolver = new PathMatchingResourcePatternResolver();
ssfBean.setMapperLocations(rpResolver.getResources("classpath:ds1/*.xml"));
ssfBean.setTypeAliasesPackage("com.yyh.mybatis.dao1");
return ssfBean;
}
@Bean("ds1_tm") //创建事务管理器
public PlatformTransactionManager transactionManager(@Qualifier("ds1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
<3>Dao类:
AmountMapper.java:
@Mapper//操作金额表的Dao类
public interface AmountMapper {
Amount query(int userNo);
void update(Amount amount);
}
MsgTableMapper.java:
@Mapper //操作消息表的Dao类
public interface MsgTableMapper {
Integer insert(MsgTable msgTable);
List<MsgTable> queryAll(int payStatus);
void update(MsgTable msgTable);
}
OrderMapper.java:
@Mapper //操作订单表的Dao类
public interface OrderMapper {
Order query(Integer id);
void update(Order order);
}
<4>Mapper映射文件:
AmountMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyh.mybatis.dao0.AmountMapper">
<select id="query" resultType="com.yyh.mybatis.domain.Amount" parameterType="int" resultMap="resultMapId">
select * from t_amount where user_no=#{userNo}
</select>
<update id="update" parameterType="com.yyh.mybatis.domain.Amount">
update t_amount set amount=#{amount} where user_no=#{userNo}
</update>
<resultMap id="resultMapId" type="com.yyh.mybatis.domain.Amount">
<result column="user_no" property="userNo" />
</resultMap>
</mapper>
MsgTableMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyh.mybatis.dao0.MsgTableMapper"> <!-- 配置对象关系映射 -->
<insert id="insert" parameterType="com.yyh.mybatis.domain.MsgTable" keyProperty="id" useGeneratedKeys="true"> <!-- id为方法名,parameterType传参类型-->
insert into t_msg_table(order_id,pay_status,retry_count,user_no,create_time,update_time)
values(#{orderId},#{payStatus},#{retryCount},#{userNo},#{createTime},#{updateTime}) <!-- #{}类似?占位符,将对象属性值存入对应的同名列中 -->
</insert>
<select id="queryAll" resultType="com.yyh.mybatis.domain.MsgTable" parameterType="int" resultMap="resultMapId">
select * from t_msg_table where pay_status=#{payStatus}
</select>
<update id="update" parameterType="com.yyh.mybatis.domain.MsgTable">
update t_msg_table set pay_status=#{payStatus},retry_count=#{retryCount},update_time=#{updateTime} where order_id=#{orderId}
</update>
<resultMap id="resultMapId" type="com.yyh.mybatis.domain.MsgTable">
<result column="order_id" property="orderId" />
<result column="pay_status" property="payStatus" />
<result column="retry_count" property="retryCount" />
<result column="user_no" property="userNo" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
</mapper>
OrderMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yyh.mybatis.dao1.OrderMapper"> <!-- 配置对象关系映射 -->
<select id="query" resultType="com.yyh.mybatis.domain.Order" parameterType="int" resultMap="resultMapId">
select * from t_order where id=#{id}
</select>
<update id="update" parameterType="com.yyh.mybatis.domain.Order">
update t_order set pay_status=#{payStatus} where id=#{id}
</update>
<resultMap id="resultMapId" type="com.yyh.mybatis.domain.Order">
<result column="pay_status" property="payStatus" />
<result column="user_no" property="userNo" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
</mapper>
(4)测试效果:
说明:t_order表中已有orderId=1的订单数据,t_amount表中已有userNo=1000的金额数据。访问以下支付接口,数秒后可以看到t_order表pay_status状态已经被定时任务自动更新为0(成功)。
http://localhost:9998/pay/pay?userNo=1000&orderId=1&amount=10
3.使用MQ方式处理分布式事务:
说明:
流程与本地消息表一样,需要向MQ发送更新订单支付状态的消息(替换本地消息表),由MQ消费者接收消息并更新订单状态(替换定时任务)。适用公司内系统。
MQ安装(IP为146,第一章1小节Centos7系统上安装):
https://blog.csdn.net/a526001650a/article/details/107978792
Mysql0主机ds0数据库-金额表t_amount结构:
id(主键)、user_no(用户id)、amount(金额,decimal类型)、create_time(创建时间)、update_time(更新时间)...
Mysql1主机ds1数据库-订单表t_order结构:
id(主键,订单id)、amount(金额,decimal类型)、pay_status(支付状态)、user_no(用户id)、create_time(创建时间)、update_time(更新时间)...
(1)金额表扣钱+向MQ发送已支付信息:
AmountService.java(金额表操作与发送MQ消息业务类):
@Service
public class AmountService { //操作金额表与向MQ发送已支付消息
@Resource
private AmountMapper amountMapper; //操作mysql0主机ds0数据库
@Autowired
private MsgSendBiz msgSendBiz;
@Transactional(transactionManager = "ds0_tm", rollbackFor = Exception.class) //引入DS0Config类中创建的tm0
public int subtract(int userNo, int orderId, BigDecimal amount) {
//1.金额表-扣钱
Amount a = amountMapper.query(userNo);
if (a == null) return Constants.AMOUNT_ERROR; //此用户没有金额记录
if (a.getAmount().compareTo(amount) < 0) return Constants.AMOUNT_NO_MONEY; //余额不足
a.setAmount(a.getAmount().subtract(amount)); //a.getAmount() - amount
amountMapper.update(a);
//2.向MQ发送已支付信息
try {
msgSendBiz.sendPayStatusMsg(String.valueOf(orderId));
} catch (Exception e) {
throw e; //消息发送异常时金额表-扣钱进行回滚
}
return Constants.AMOUNT_SUCCESS;
}
}
MsgSendBiz.java(MQ消息发送类):
@Component
public class MsgSendBiz { //消息发送类
@Autowired
private RocketMQTemplate rocketMQTemplate; //RocketMQ消息发送类
public void sendPayStatusMsg(String orderId) {//1.发送已支付消息
rocketMQTemplate.convertAndSend("topic_pay_status", orderId);
}
}
PayController.java(支付相关接口):
同消息表小节的PayController.java类。
(2)实现MQ消息接收类,接收MQ已支付消息,将已支付状态更新到订单表:
MsgReceive.java(MQ消息接收类):
@Component
@RocketMQMessageListener(consumerGroup = "pay_group", //组名称
topic = "topic_pay_status", //订阅名称
selectorExpression = "*") //topic_pay_status订阅名称下的消息
public class MsgReceive implements RocketMQListener<String> { //消息接收类
@Autowired
private OrderService orderService;
@Override
public void onMessage(String orderId) {//2.接收消息
System.out.println("收到消息 orderId: " + orderId);
orderService.updatePayStatus(Integer.parseInt(orderId));
}
}
(3)其他基础配置:
<1>pom.xml依赖:
<dependencies>
...
<!-- 导入rocketmq依赖包 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.1</version>
</dependency>
</dependencies>
<2>数据源:
同消息表小节的数据源。
<3>Dao类:
同消息表小节的Dao类。