Springboot整合RocketMQ解决分布式事务
直接上代码:
代码结构如下:
依次贴出相关类:
DataSource1Config:
package com.example.demo.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
//DataSource01
@Configuration // 注册到springboot容器中
@MapperScan(basePackages = "com.example.demo.test01", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSource1Config {
@Bean(name = "test1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test1")
@Primary
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new
// PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
@Bean(name = "test1TransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
DataSource2Config:
package com.example.demo.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
//DataSource2
@Configuration // 注册到springboot容器中
@MapperScan(basePackages = "com.example.demo.test02", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSource2Config {
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new
// PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test2/*.xml"));
return bean.getObject();
}
@Bean(name = "test2TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
OrderConsumer:
package com.example.demo.consumer;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.DispatcherEntity;
import com.example.demo.entity.OrderEntity;
import com.example.demo.test02.DispatcherMapper;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @description: 消费者
* @author: dfz
* @create: 2020-02-24
**/
@Service
@RocketMQMessageListener(topic = "orderTopic", consumerGroup = "springboot-consumer-group")
public class OrderConsumer implements RocketMQListener<String>{
@Autowired
private DispatcherMapper dispatcherMapper;
@Override
public void onMessage(String msg) {
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
String orderId = orderEntity.getOrderId();
DispatcherEntity dispatcherEntity = new DispatcherEntity(orderId, "123456");
dispatcherMapper.insertDistribute(dispatcherEntity);
}
}
ProducerController:
package com.example.demo.producer;
import com.example.demo.service.ProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
@RestController
public class ProducerController {
@Autowired
private ProducerService producerService;
@RequestMapping(value = "sendMsg")
public String insertUser(){
producerService.saveOrder();
return "success";
}
}
ProducerService:
package com.example.demo.service;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.OrderEntity;
import com.example.demo.test01.OrderMapper;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @description: ProducerService
* @author: dfz
* @create: 2020-02-24
**/
@Service
public class ProducerService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RocketMQTemplate rocketMQTemplate;
public String saveOrder(){
//提前生成订单id
String orderId = System.currentTimeMillis()+"";
//提前生成半消息,再执行本地事务
OrderEntity orderEntity = creatOrder(orderId);
String msg = JSONObject.toJSONString(orderEntity);
MessageBuilder<String> tMessageBuilder = MessageBuilder.withPayload(msg);
tMessageBuilder.setHeader("msg", msg);
Message message = tMessageBuilder.build();
//该消息不会被消费
rocketMQTemplate.sendMessageInTransaction("springboot-producer-group", "orderTopic", message, null);
return orderId;
}
public OrderEntity creatOrder(String orderId){
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderId(orderId);
orderEntity.setOrderName("订单Id1234");
orderEntity.setOrderCreateDate(new Date());
orderEntity.setPrice(300d);
return orderEntity;
}
}
package com.example.demo.listener;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.OrderEntity;
import com.example.demo.test01.OrderMapper;
import com.example.demo.utils.TransactionalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
@Slf4j
@Component
@RocketMQTransactionListener(txProducerGroup = "springboot-producer-group")
public class SyncProducerListener implements RocketMQLocalTransactionListener {
@Autowired
private TransactionalUtil transactionalUtil;
@Autowired
private OrderMapper orderMapper;
//执行本地事务
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
MessageHeaders headers = message.getHeaders();
Object object = headers.get("msg");
if (object == null) {
return RocketMQLocalTransactionState.ROLLBACK;
}
String orderMessage = (String) object;
OrderEntity orderEntity = JSONObject.parseObject(orderMessage, OrderEntity.class);
TransactionStatus status = null;
try {
status = transactionalUtil.begin();
int result = orderMapper.addOrder(orderEntity);
transactionalUtil.commit(status);
if (result < 1) {
return RocketMQLocalTransactionState.ROLLBACK;
}
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
if (status != null) {
transactionalUtil.rollback(status);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
return null;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
MessageHeaders headers = message.getHeaders();
Object object = headers.get("msg");
if (object == null) {
return RocketMQLocalTransactionState.ROLLBACK;
}
String orderMessage = (String) object;
OrderEntity orderEntity = JSONObject.parseObject(orderMessage, OrderEntity.class);
String orderId = orderEntity.getOrderId();
//查询数据库
OrderEntity orderEntity1 = orderMapper.findOrder(orderId);
if (null == orderEntity1) {
return RocketMQLocalTransactionState.UNKNOWN;
}
return RocketMQLocalTransactionState.COMMIT;
}
}
package com.example.demo.test01;
import com.example.demo.entity.OrderEntity;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
public interface OrderMapper {
@Insert("insert into order values(null,#{order.orderId},#{order.orderName}, #{order.price}, #{order.orderCreateDate});")
public int addOrder(OrderEntity order);
@Insert("select * from order where orderId = ${orderId};")
public OrderEntity findOrder(String orderId);
}
package com.example.demo.test02;
import com.example.demo.entity.DispatcherEntity;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
public interface DispatcherMapper {
@Insert("insert into order_users values(null,#{entity.name},#{entity.age});")
public int insertDistribute(DispatcherEntity entity);
}
package com.example.demo.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
@Service
public class TransactionalUtil {
@Autowired
public DataSourceTransactionManager transactionManager;
public TransactionStatus begin(){
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionAttribute());
return transactionStatus;
}
public void commit(TransactionStatus status){
transactionManager.commit(status);
}
public void rollback(TransactionStatus status){
transactionManager.rollback(status);
}
}
package com.example.demo.entity;
import lombok.Data;
import java.io.Serializable;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-24
**/
@Data
public class DispatcherEntity implements Serializable {
private String orderId;
private String userId;
public DispatcherEntity(String orderId, String userId) {
this.orderId = orderId;
this.userId = userId;
}
}
package com.example.demo.entity;
import java.io.Serializable;
import java.util.Date;
/**
* @description: ${description}
* @author: dfz
* @create: 2020-02-20
**/
public class OrderEntity implements Serializable{
private String orderId;
private String orderName;
private Date orderCreateDate;
private Double price;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Date getOrderCreateDate() {
return orderCreateDate;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public void setOrderCreateDate(Date orderCreateDate) {
this.orderCreateDate = orderCreateDate;
}
public OrderEntity(String orderId, String orderName) {
this.orderId = orderId;
this.orderName = orderName;
}
public OrderEntity() {
}
}
server.port=8088
###datasource1
spring.datasource.test1.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.test1.username = root
spring.datasource.test1.password = 123456
###datasource2
spring.datasource.test2.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url = jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.test2.username = root
spring.datasource.test2.password = 123456
rocketmq.name-server=192.168.2.3:9876
rocketmq.producer.group=boot_sync_user
Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.demo</groupId>
<artifactId>rocketmq-transactional</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rocketmq-transactional</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- springboot-web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot 整合 pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!--RocketMQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>