Springcloud集成Seata分布式事务

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分布式的回滚魅力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值