Springcloud集成Seata分布式事务
一、环境
开发工具:idea
springcloud版本:Hoxton.SR12
springboot版本:2.3.12.RELEASE
springcloud alibaba版本:2.2.7.RELEASE
seata版本:2.2.7.RELEASE
Seata单机部署、 Seata集群部署 和 nginx代理nacos集群
二、pom文件依赖
主要给出核心依赖,其他依赖自行引入,不如springcloud、springcloudalibaba等等
<?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">
……
<dependencies>
<!--nacos 服务注册/发现 客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--seata分布式事务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
……
</project>
三、applicaton.yml配置
配置文件关于nacos的配置,可以参考Nginx代理Nacos集群进行配置
自行配置数据配置和端口信息
# spring 相关配置
spring:
cloud:
alibaba:
seata:
# seata分组配置
tx-service-group: my_test_tx_group
# seata分布式事务配置
seata:
# 注册中心配置
registry:
# 注册中心类型,这里采用alibaba的nacos
type: nacos
nacos:
# 服务名
application: seata-server
# nacos注册中的地址,此处配置多个时默认集群模式
server-addr: 192.168.0.28:8844,192.168.0.28:8846,192.168.0.28:8848
# nacos用户密码
username: nacos
password: nacos
# 分组名
group: SEATA_GROUP
# 命名空间ID值
namespace: 637b036c-75a2-4afb-9ccd-2716ece04ffd
# 配置中心
config:
# 配置中心类型,这里采用alibaba的nacos
type: nacos
nacos:
# 配置中心地址,此处配置多个时模式集群模式
server-addr: 192.168.0.28:8844,192.168.0.28:8846,192.168.0.28:8848
# 配置中心用户名和密码
username: nacos
password: nacos
# 分组名
group: SEATA_GROUP
# 命名空间
namespace: 637b036c-75a2-4afb-9ccd-2716ece04ffd
四、商品服务
- OrderController.java
package com.jwssw.order.controller;
import com.jwssw.order.provider.StockServiceProvider;
import com.jwssw.order.service.OrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 类描述:对外提供服务类
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/3/19 10:43
* @since JDK 8
*/
@RestController
@RequestMapping("/order")
public class OrderController {
/** restTemplate远程调用对象 */
private final RestTemplate restTemplate;
/** openfeign远程调用对象 */
private final StockServiceProvider stockService;
/** 订单服务接口 */
private final OrderService orderService;
/**
* 构造方法注入
*/
public OrderController(RestTemplate restTemplate, StockServiceProvider stockService, OrderService orderService) {
this.restTemplate = restTemplate;
this.stockService = stockService;
this.orderService = orderService;
}
/**
* 增加订单同时减少库存
*/
@RequestMapping("/insert")
public String insert(@RequestParam("num") int num) {
// 调用接口
return orderService.reduct(num);
}
}
- OrderService.java
package com.jwssw.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jwssw.order.domain.entity.JwsswOrder;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 16:26
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
public interface OrderService extends IService<JwsswOrder> {
// 增加订单同时减少库存
String reduct(int num);
}
- OrderServiceImpl.java
@GlobalTransactional 该注解是seata实现分布式事务的关键注解,将该注解直接作用于方法上即可。
package com.jwssw.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jwssw.order.domain.entity.JwsswOrder;
import com.jwssw.order.mapper.OrderMapper;
import com.jwssw.order.provider.StockServiceProvider;
import com.jwssw.order.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Random;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 16:26
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, JwsswOrder> implements OrderService {
/** restTemplate远程调用对象 */
private final RestTemplate restTemplate;
private final StockServiceProvider stockServiceProvider;
/**
* 构造方法注入
*/
public OrderServiceImpl(RestTemplate restTemplate, StockServiceProvider stockServiceProvider) {
this.restTemplate = restTemplate;
this.stockServiceProvider = stockServiceProvider;
}
@GlobalTransactional // 该注解是seata实现分布式事务的关键主键
@Override
public String reduct(int num) {
// 本地数据库保存订单
JwsswOrder order = new JwsswOrder();
order.setId(new Random().nextInt(100000));
order.setContext("cethis");
order.setCreateDate(new Date().toString());
baseMapper.insert(order);
// 调用库存微服务接口(openfeign远程调用)
String reduct = stockServiceProvider.reduce(1);
// 模拟失败,主要是除0
int i = 1 / num;
return reduct;
}
}
- StockServiceProvider.java
该类作用就是通过openfeign调用库存服务中的接口
package com.jwssw.order.provider;
import com.jwssw.order.provider.fallback.StockServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 类描述:openfeign远程调用接口类
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/3/24 09:53
* @since JDK 8
*/
@FeignClient(name = "stock-server", fallbackFactory = StockServiceFallback.class)
public interface StockServiceProvider {
/**
* 方法描述: 调用stock服务的reduct接口的方法
*
* @return {@link String}
* @author 鲁浩鹏 Lu Haopeng
* @date 2022/3/25 14:23
*/
@PostMapping("/stock/reduce")
String reduce(@RequestParam("stockId") int stockId);
}
- StockServiceFallback
package com.jwssw.order.provider.fallback;
import com.jwssw.order.provider.StockServiceProvider;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 16:33
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
@Component
public class StockServiceFallback implements FallbackFactory<StockServiceFactory> {
@Override
public StockServiceFactory create(Throwable cause) {
return new StockServiceFactory();
}
}
/**
* 错误回调工厂类
*/
class StockServiceFactory implements StockServiceProvider {
/**
* 方法描述: 调用stock服务的reduct接口的方法
*
* @return {@link String}
* @author 鲁浩鹏 Lu Haopeng
* @date 2022/3/25 14:23
*/
@Override
public String reduce(int stockId) {
return "库存服务不可达";
}
}
- JwsswOrder.java
package com.jwssw.order.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 16:02
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@TableName("jwssw_order")
public class JwsswOrder implements Serializable {
@TableId
private int id;
private String createDate;
private String context;
}
- OrderMapper.java
package com.jwssw.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jwssw.order.domain.entity.JwsswOrder;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 16:22
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
public interface OrderMapper extends BaseMapper<JwsswOrder> {
}
五、库存服务
- StockController.java
package com.jwssw.stock.controller;
import com.jwssw.stock.domain.entity.JwsswStock;
import com.jwssw.stock.service.StockService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 类描述:对外提供服务类
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/3/19 10:46
* @since JDK 8
*/
@RestController
@RequestMapping("/stock")
public class StockController {
/** 获取配置文件中的端口号 */
@Value("${server.port}")
private String port;
private final StockService stockService;
public StockController(StockService stockService) {
this.stockService = stockService;
}
@PostMapping("/reduce")
public String reduce(@RequestParam("stockId") int stockId) {
// todo 为了演示方便写在此处了,正式项目中需要写在service中
boolean update = stockService.lambdaUpdate().setSql("stock_number=stock_number -1")
.eq(JwsswStock::getId, stockId).update();
return update ? "扣减库存成功" : "扣减库存失败";
}
}
库存的SQL语句
INSERT INTO `jwssw_stock` (`id`, `order_name`, `stock_number`) VALUES (1, '芬太尼', 77);
- StockService.java
package com.jwssw.stock.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jwssw.stock.domain.entity.JwsswStock;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 18:32
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
public interface StockService extends IService<JwsswStock> {
}
- StockServiceImpl.java
package com.jwssw.stock.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jwssw.stock.domain.entity.JwsswStock;
import com.jwssw.stock.mapper.StockMapper;
import com.jwssw.stock.service.StockService;
import org.springframework.stereotype.Service;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 18:33
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, JwsswStock> implements StockService {
}
- StockMapper.java
package com.jwssw.stock.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jwssw.stock.domain.entity.JwsswStock;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 18:32
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
public interface StockMapper extends BaseMapper<JwsswStock> {
}
- JwsswStock.java
package com.jwssw.stock.domain.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
/**
* 类描述:
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/4/5 18:30
* @slogan 优良的架构是演进而来的,非设计出来的
* @since JDK 8
*/
@Data
@Builder
@TableName("jwssw_stock")
public class JwsswStock {
private int id;
private String orderName;
private int stockNumber;
}
六、测试
单元测试类是JUnti5
- OrderControllerTest.java
package com.jwssw.order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* 类描述:单元测试类
*
* @author 鲁浩鹏 Lu Haopeng
* @version 1.0
* @email <a href="mailTo:luhaopeng2005@126.com">Lu Haopeng</a>
* @date 2022/3/24 09:26
* @since JDK 8
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class OrderControllerTest {
@Autowired
private MockMvc mvc;
@Test
void add() throws Exception {
// 调用controller中的接口
ResultActions resultActions = mvc.perform(get("/order/insert").param("num", "1"))
.andDo(print())
.andExpect(status().isOk());
// 输出结果
System.out.println(resultActions.andReturn().getResponse().getContentAsString());
}
}
小伙伴们自行运行单元测试类时,需要修改num参数值(num=0是回滚)来实验seata分布式的回滚魅力。