JavaEE:使用分布式事务

一、使用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类。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值