seata + nacos + openfeign

  • 使用nacos做注册中心和配置中心
  • 使用openfeign做http调用
  • mysql 业务库:seata_order、seata_stock

1. 创建业务库

1.1. seata_order

CREATE TABLE `order_tbl` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int DEFAULT '0',
  `money` int DEFAULT '0',
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.2. seata_stock

CREATE TABLE `stock_tbl` (
  `id` int NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int DEFAULT '0',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `seata_stock`.`stock_tbl` (`id`, `commodity_code`, `count`, `update_time`) VALUES ('1', 'product-1', '-17', '2023-01-30 09:07:36');
INSERT INTO `seata_stock`.`stock_tbl` (`id`, `commodity_code`, `count`, `update_time`) VALUES ('2', 'product-2', '-7', '2023-01-30 01:39:01');

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. demo项目

  • parent项目 springboot-openfeign-seataat
  • 三个子项目
    • rm-stock,使用seata_stock库。扣除商品,更新stock_tbl表
    • rm-order,使用seata_order库。新建订单,向order_tbl表插入记录
    • tm-portal,调用rm-order下单,调用rm-stock扣除商品

2.1. parent项目

删除src目录,修改pom.xml

<groupId>com.cccc</groupId>
    <artifactId>springboot-openfeign-seataat</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>rm-stock</module>
        <module>rm-order</module>
        <module>tm-portal</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
        <spring-boot.version>2.6.11</spring-boot.version>
        <spring.cloud.alibaba.version>2021.0.4.0</spring.cloud.alibaba.version>

        <seata.version>1.4.2</seata.version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Maven properties -->
        <mysql-connector.version>5.1.44</mysql-connector.version>
        <guava.version>27.0.1-jre</guava.version>
        <jackson.version>2.13.4.1</jackson.version>
        <druid.version>1.1.12</druid.version>
        <jakarta-annotation-api.version>1.3.5</jakarta-annotation-api.version>
        <javax.annotation-api.version>1.3.1</javax.annotation-api.version>
        <lombok.version>1.18.8</lombok.version>

        <maven-source-plugin.version>3.0.1</maven-source-plugin.version>
        <maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
        <mybatis-plus-boot-starter.version>3.3.0</mybatis-plus-boot-starter.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--mybatis-spring-boot-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.0</version>
            </dependency>
            <!--druid-spring-boot连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.11</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2. rm-stock

  1. pom.xml
<parent>
        <artifactId>springboot-openfeign-seataat</artifactId>
        <groupId>com.cccc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>rm-stock</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- Seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>${spring.cloud.alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus-boot-starter.version}</version>
        </dependency>
        <!-- mybatis -->

        <!--mybatis-generator-->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>
        <!--mybatis-generator-->

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-slf4j</artifactId>
            <version>11.8</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
  1. applicaion.properties
spring.application.name=stock-service
server.port=9091
# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr = xx.xx.xx.xx:8848
spring.cloud.nacos.discovery.namespace = nnnnn
spring.cloud.nacos.discovery.register-enabled = true
spring.cloud.nacos.discovery.username = uuuuu
spring.cloud.nacos.discovery.password=ppppp
spring.cloud.nacos.discovery.group=SEATA_GROUP

# 数据源配置
spring.datasource.url=jdbc:mysql://xx.xx.xx.xx:3306/seata_stock?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=ppppp
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#事务分组配置(在v1.5之后默认值为default_tx_group)
seata.tx-service-group=seata-storage-service
#指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)
seata.service.vgroup-mapping.seata-storage-service=default
#使用nacos作为注册中心
seata.registry.type=nacos
#nacos注册中心IP:端口
seata.registry.nacos.server-addr=xx.xx.xx.xx:8848
seata.registry.nacos.username = uuuuu
seata.registry.nacos.password = ppppp
seata.registry.nacos.namespace = nnnnn
seata.registry.nacos.cluster = default
#Seata服务名(应与seata-server实际注册的服务名一致)
seata.registry.nacos.application=seata-server
#Seata分组名(应与seata-server实际注册的分组名一致)
seata.registry.nacos.group=SEATA_GROUP

#log
#logging.level.io.seata=debug
#logging.level.org.springframework = debug

3.启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class StockServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(StockServiceApplication.class, args);
    }
}
  1. 实体类
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(chain = true)
@TableName("stock_tbl")
public class Stock {
    private Long id;
    private String commodityCode;
    private Long count;
    private Date updateTime;
}
  1. dao
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cccc.entity.Stock;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface StockDAO extends BaseMapper<Stock> {
}
  1. service
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cccc.entity.Stock;
import com.cccc.repository.StockDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;

@Service
public class StockService {

    @Resource
    private StockDAO stockDAO;

    /**
     * 减库存
     *
     * @param commodityCode
     * @param count
     */
    @Transactional(rollbackFor = Exception.class)
    public void deduct(String commodityCode, int count) {
        if (commodityCode.equals("product-2")) {
            throw new RuntimeException("异常:模拟业务异常:stock branch exception");
        }

        QueryWrapper<Stock> wrapper = new QueryWrapper<>();
        wrapper.setEntity(new Stock().setCommodityCode(commodityCode));
        Stock stock = stockDAO.selectOne(wrapper);
        stock.setCount(stock.getCount() - count);
        stock.setUpdateTime(new Date());

        stockDAO.updateById(stock);
    }
}
  1. controller
import com.cccc.service.StockService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("stock")
public class StockController {

    @Resource
    private StockService stockService;

    /**
     * 减库存
     *
     * @param commodityCode 商品代码
     * @param count         数量
     * @return
     */
    @RequestMapping(path = "/deduct")
    public Boolean deduct(String commodityCode, Integer count) {
        stockService.deduct(commodityCode, count);
        return true;
    }
}

2.3. rm-order

  1. pom.xml
<parent>
        <artifactId>springboot-openfeign-seataat</artifactId>
        <groupId>com.cccc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>rm-order</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- Seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>${spring.cloud.alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus-boot-starter.version}</version>
        </dependency>
        <!-- mybatis -->

        <!--mybatis-generator-->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>
        <!--mybatis-generator-->

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-slf4j</artifactId>
            <version>11.8</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
  1. applicaion.properties
spring.application.name=order-service
server.port=9092
# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr = xx.xx.xx.xx:8848
spring.cloud.nacos.discovery.namespace = nnnnn
spring.cloud.nacos.discovery.register-enabled = true
spring.cloud.nacos.discovery.username = uuuuu
spring.cloud.nacos.discovery.password=ppppp
spring.cloud.nacos.discovery.group=SEATA_GROUP

# 数据源配置
spring.datasource.url=jdbc:mysql://xx.xx.xx.xx:3306/seata_order?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=uuuuu
spring.datasource.password=ppppp
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#事务分组配置(在v1.5之后默认值为default_tx_group)
seata.tx-service-group=seata-storage-service
#指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)
seata.service.vgroup-mapping.seata-storage-service=default
#使用nacos作为注册中心
seata.registry.type=nacos
#nacos注册中心IP:端口
seata.registry.nacos.server-addr=xx.xx.xx.xx:8848
seata.registry.nacos.username = uuuuu
seata.registry.nacos.password = ppppp
seata.registry.nacos.namespace = nnnnn
seata.registry.nacos.cluster = default
#Seata服务名(应与seata-server实际注册的服务名一致)
seata.registry.nacos.application=seata-server
#Seata分组名(应与seata-server实际注册的分组名一致)
seata.registry.nacos.group=SEATA_GROUP
logging.level.io.seata=debug

3.启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

  1. 实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.math.BigDecimal;
import java.util.Date;

@Data
@Accessors(chain = true)
@TableName("order_tbl")
public class Order {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String userId;
    private String commodityCode;
    private Integer count;
    private BigDecimal money;
    private Date createTime;
}
  1. dao
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cccc.model.Order;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface OrderDAO extends BaseMapper<Order> {
}
  1. service
import com.cccc.model.Order;
import com.cccc.repository.OrderDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;

@Service
public class OrderService {

    @Resource
    private OrderDAO orderDAO;

    /**
     * 创建订单
     *
     * @param userId
     * @param commodityCode
     * @param count
     */
    @Transactional(rollbackFor = Exception.class)
    public void placeOrder(String userId, String commodityCode, Integer count) {
        BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
        Order order = new Order().setUserId(userId).setCommodityCode(commodityCode).setCount(count)
                .setMoney(orderMoney).setCreateTime(new Date());
        orderDAO.insert(order);
    }

}
  1. controller
import com.cccc.service.OrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("order")
public class OrderController {

    @Resource
    private OrderService orderService;

    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/placeOrder/commit")
    public Boolean placeOrderCommit(String userId, String commodityCode, Integer count) {
        orderService.placeOrder(userId, commodityCode, count);
        return true;
    }
}

2.4. rm-portal

  1. pom.xml
<parent>
        <artifactId>springboot-openfeign-seataat</artifactId>
        <groupId>com.cccc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>tm-portal</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- Seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>${spring.cloud.alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-slf4j</artifactId>
            <version>11.8</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
  1. applicaion.properties
spring.application.name=portal-service
server.port=9090
# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr = xx.xx.xx.xx:8848
spring.cloud.nacos.discovery.namespace = nnnnn
spring.cloud.nacos.discovery.register-enabled = true
spring.cloud.nacos.discovery.username = uuuuu
spring.cloud.nacos.discovery.password=ppppp
spring.cloud.nacos.discovery.group=SEATA_GROUP


#事务分组配置(在v1.5之后默认值为default_tx_group)
seata.tx-service-group=seata-storage-service
#指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)
seata.service.vgroup-mapping.seata-storage-service=default
#使用nacos作为注册中心
seata.registry.type=nacos
#nacos注册中心IP:端口
seata.registry.nacos.server-addr=xx.xx.xx.xx:8848
seata.registry.nacos.username = uuuuu
seata.registry.nacos.password = ppppp
seata.registry.nacos.namespace = nnnnn
seata.registry.nacos.cluster = default
#Seata服务名(应与seata-server实际注册的服务名一致)
seata.registry.nacos.application=seata-server
#Seata分组名(应与seata-server实际注册的分组名一致)
seata.registry.nacos.group=SEATA_GROUP
logging.level.io.seata=debug

3.启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class PortalApplication {
    public static void main(String[] args) {
        SpringApplication.run(PortalApplication.class, args);
    }
}

  1. feign
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "order-service")
public interface OrderFeignClient {

    @GetMapping("order/placeOrder/commit")
    Boolean placeOrderCommit(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);

}
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "stock-service")
public interface StockFeignClient {

    @GetMapping("stock/deduct")
    Boolean deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}
  1. service
import com.cccc.feign.OrderFeignClient;
import com.cccc.feign.StockFeignClient;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class PortalService {

    @Resource
    private StockFeignClient stockFeignClient;
    @Resource
    private OrderFeignClient orderFeignClient;

    /**
     * 下单:创建订单、减库存,涉及到两个服务
     *
     * @param userId
     * @param commodityCode
     * @param count
     */
    @GlobalTransactional
    public void placeOrder(String userId, String commodityCode, Integer count) {
        orderFeignClient.placeOrderCommit(userId, commodityCode, count);
        stockFeignClient.deduct(commodityCode, count);
    }

    /**
     * 下单:创建订单、减库存,涉及到两个服务
     *
     * @param userId
     * @param commodityCode
     * @param count
     */
    public void placeOrder2(String userId, String commodityCode, Integer count) {
        orderFeignClient.placeOrderCommit(userId, commodityCode, count);
        stockFeignClient.deduct(commodityCode, count);
    }

}
  1. controller
import com.cccc.service.PortalService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("portal")
public class PortalController {

    @Resource
    private PortalService portalService;

    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/commit")
    public Boolean placeOrderCommit() {
        portalService.placeOrder("1", "product-1", 1);
        return true;
    }

    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/rollback")
    public Boolean placeOrderRollback() {
        // product-2 扣库存时模拟了一个业务异常,
        portalService.placeOrder("1", "product-2", 1);
        return true;
    }

    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/commit2")
    public Boolean placeOrderCommit2() {
        portalService.placeOrder2("1", "product-1", 1);
        return true;
    }

    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/rollback2")
    public Boolean placeOrderRollback2() {
        // product-2 扣库存时模拟了一个业务异常,
        portalService.placeOrder2("1", "product-2", 1);
        return true;
    }

}

3. 演示

3.1. 运行rm-order、rm-stock、tm-portal

3.2. tm-portal提供4个url

  1. http://localhost:9090/portal/commit
    开分布式事务,下单成功,扣除商品成功。order_tbl表有新记录,stock_tbl表商品数量减少
  2. http://localhost:9090/portal/rollback
    开分布式事务,下单成功,扣除商品抛异常。order_tbl表无新记录,stock_tbl表商品数量不变
    rm-order、rm-stock的数据库操作都回滚掉了
  3. http://localhost:9090/portal/commit2
    不开分布式事务,下单成功,扣除商品成功。order_tbl表有新记录,stock_tbl表商品数量减少
  4. http://localhost:9090/portal/rollback2
    不开分布式事务,下单成功,扣除商品抛异常。order_tbl表有新记录,stock_tbl表商品数量不变
    rm-order的数据库操作没有回滚掉了

对比2和4,可以看出seata的分布式事务效果

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容