首页 > 其他分享 >Seata系列之(四)实战

Seata系列之(四)实战

时间:2024-02-28 11:25:28浏览次数:22  
标签:实战 系列 Seata atguigu springframework alibaba org import com

实战

1. 分布式事务业务说明

​ 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
​ 当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
​ 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

即:下订单-->扣库存-->减账户(余额)-->改状态

2. 构建业务数据库
-- 存储订单的数据库
CREATE DATABASE seata_order;
use seata_order;

-- 业务表t_order
CREATE TABLE t_order(
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    `count` INT(11) DEFAULT NULL COMMENT '数量',
    `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
    `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;

-- 存储库存的数据库
CREATE DATABASE seata_storage;

use seata_storage;
-- 业务表t_storage
CREATE TABLE t_storage(
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
   `total` INT(11) DEFAULT NULL COMMENT '总库存',
    `used` INT(11) DEFAULT NULL COMMENT '已用库存',
    `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');

-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;

-- 存储账户信息的数据库
CREATE DATABASE seata_account;
use seata_account;
-- 业务表t_account
CREATE TABLE t_account(
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
    `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
    `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
    `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000');

-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;
3. 构建业务微服务
3.1 订单微服务
  • 建Module【seata-order-service2001】

  • 改POM

    <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud2020</artifactId><groupId>com.atguigu.springcloud</groupId><version>1.0-SNAPSHOT</version></parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>seata-order-service2001</artifactId>
    
    <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata:为了保证版本一致,剔除了自带的Seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 版本要和我们安装的Seata-Server版本一致 -->
         <dependency>
             <groupId>io.seata</groupId>
             <artifactId>seata-all</artifactId>
             <version>0.9.0</version>
         </dependency>
         <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--mysql-druid-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    
  • 写YML

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        alibaba:
          seata:
            #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应
            tx-service-group: fsp_tx_group
        nacos:
          discovery:
            server-addr: 192.168.59.128:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.59.128:3306/seata_order
        username: root
        password: rootroot
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
  • 在resources下创建file.conf

    从Seata安装目录下的conf下拿过来(file.conf的原始文件,即备份的文件),做如下修改

  • 在resources下创建registry.conf

    从Seata安装目录下的conf下拿过来(将安装时修改后的文件拿过来,改下ip即可)

  • 创建domain【实体类】

    • CommonResult

      package com.atguigu.springcloud.alibaba.domain;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class CommonResult<T> {
          private Integer code;
          private String  message;
          private T       data;
      
          public CommonResult(Integer code, String message){
              this(code,message,null);
          }
      }
      
    • Order

      我这里是通过mybatis generater插件生成的,如果连接数据库时数据库不允许远程访问,执行

      set global validate_password_policy=LOW;
      GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'rootroot' WITH GRANT OPTION;
      FLUSH   PRIVILEGES;
      
      package com.atguigu.springcloud.alibaba.domain;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Order {
          private Long id;
      
          /**
           * 用户id
           */
          private Long userId;
      
          /**
           * 产品id
           */
          private Long productId;
      
          /**
           * 数量
           */
          private Integer count;
      
          /**
           * 金额
           */
          private Long money;
      
          /**
           * 订单状态:0:创建中; 1:已完结
           */
          private Integer status;
      }
      
  • Dao接口及实现

    • OrderDao

      package com.atguigu.springcloud.alibaba.dao;
      
      import com.atguigu.springcloud.alibaba.domain.Order;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      
      @Mapper
      public interface OrderDao
      {
          //新建订单
          void create(Order order);
      
          //修改订单状态,从零改为1
          void update(@Param("userId") Long userId,@Param("status") Integer status);
      }
      
    • 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.atguigu.springcloud.alibaba.domain.OrderMapper">
        <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Order">
          <id column="id" property="id" jdbcType="BIGINT"/>
              <result column="user_id" property="userId" jdbcType="BIGINT"/>
              <result column="product_id" property="productId" jdbcType="BIGINT"/>
              <result column="count" property="count" jdbcType="INTEGER"/>
              <result column="money" property="money" jdbcType="DECIMAL"/>
              <result column="status" property="status" jdbcType="INTEGER"/>
        </resultMap>
      
          <insert id="create">
            insert into t_order (id,user_id,product_id,count,money,status)
              values (null,#{userId},#{productId},#{count},#{money},0);
          </insert>
      
      <update id="update">
        update t_order set status = 1
          where user_id=#{userId} and status = #{status};
      </update>
      
      ​```
  • Service接口及实现

    • OrderService

      package com.atguigu.springcloud.alibaba.service;
      
      import com.atguigu.springcloud.alibaba.domain.Order;
      

      public interface OrderService{
      void create(Order order);
      }
      ​```

    • StorageService

      package com.atguigu.springcloud.alibaba.service;
      
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      
      @FeignClient("seata-storage-service")
      public interface StorageService {
          @PostMapping("/storage/decrease")
          void decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
      }
      
    • AccountService

      package com.atguigu.springcloud.alibaba.service;
      
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      
      import java.math.BigDecimal;
      
      @FeignClient("seata-account-service")
      public interface AccountService {
          @PostMapping("/account/decrease")
          void decrease(@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money);
      }
      
    • OrderServiceImpl

      package com.atguigu.springcloud.alibaba.service.impl;
      
      import com.atguigu.springcloud.alibaba.dao.OrderDao;
      import com.atguigu.springcloud.alibaba.domain.Order;
      import com.atguigu.springcloud.alibaba.service.AccountService;
      import com.atguigu.springcloud.alibaba.service.OrderService;
      import com.atguigu.springcloud.alibaba.service.StorageService;
      import io.seata.spring.annotation.GlobalTransactional;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      

      @Service
      @Slf4j
      public class OrderServiceImpl implements OrderService
      {
      @Resource
      private OrderDao orderDao;
      @Resource
      private StorageService storageService;
      @Resource
      private AccountService accountService;

      /**
       * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
       */
      
      @Override
      public void create(Order order){
          log.info("----->开始新建订单");
          //新建订单
          orderDao.create(order);
      
          //扣减库存
          log.info("----->订单微服务开始调用库存,做扣减Count");
          storageService.decrease(order.getProductId(),order.getCount());
          log.info("----->订单微服务开始调用库存,做扣减end");
      
          //扣减账户
          log.info("----->订单微服务开始调用账户,做扣减Money");
          accountService.decrease(order.getUserId(),order.getMoney());
          log.info("----->订单微服务开始调用账户,做扣减end");
      
      
          //修改订单状态,从零到1代表已经完成
          log.info("----->修改订单状态开始");
          orderDao.update(order.getUserId(),0);
          log.info("----->修改订单状态结束");
      
          log.info("----->下订单结束了");
      
      }
      

      }
      ​```

  • Controller

    package com.atguigu.springcloud.alibaba.controller;
    
    import com.atguigu.springcloud.alibaba.domain.CommonResult;
    import com.atguigu.springcloud.alibaba.domain.Order;
    import com.atguigu.springcloud.alibaba.service.OrderService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    

    @RestController
    public class OrderController{
    @Resource
    private OrderService orderService;

    @GetMapping("/order/create")
    public CommonResult create(Order order)
    {
        orderService.create(order);
        return new CommonResult(200,"订单创建成功");
    }
    

    }

  • 配置类

    • MyBatisConfig

      package com.atguigu.springcloud.alibaba.config;
      
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.context.annotation.Configuration;
      
      
      @Configuration
      @MapperScan({"com.atguigu.springcloud.alibaba.dao"})
      public class MyBatisConfig {
      
      }
      
    
    - DataSourceProxyConfig
    
      ```java
      package com.atguigu.springcloud.alibaba.config;
      
      import com.alibaba.druid.pool.DruidDataSource;
      import io.seata.rm.datasource.DataSourceProxy;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.mybatis.spring.SqlSessionFactoryBean;
      import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
      import javax.sql.DataSource;
    

    @Configuration
    public class DataSourceProxyConfig {

      @Value("${mybatis.mapperLocations}")
      private String mapperLocations;
    
    
      @Bean
      @ConfigurationProperties(prefix = "spring.datasource")
      public DataSource druidDataSource(){
          return new DruidDataSource();
      }
    
    
      @Bean
      public DataSourceProxy dataSourceProxy(DataSource dataSource) {
          return new DataSourceProxy(dataSource);
      }
    
    
      @Bean
      public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
          SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
          sqlSessionFactoryBean.setDataSource(dataSourceProxy);
          sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
          sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
          return sqlSessionFactoryBean.getObject();
      }
    

    }
    ​```

  • 主启动

    package com.atguigu.springcloud.alibaba;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableDiscoveryClient
    @EnableFeignClients
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建的配置
    public class SeataOrderMainApp2001{
    
        public static void main(String[] args)
        {
            SpringApplication.run(SeataOrderMainApp2001.class, args);
        }
    }
    
3.2 库存微服务
  • 建Module【seata-order-service2002】

  • 改POM

    同4.4.3.1

  • 写YML

    server:
      port: 2002
    
    spring:
      application:
        name: seata-storage-service
      cloud:
        alibaba:
          seata:
            #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应
            tx-service-group: fsp_tx_group
        nacos:
          discovery:
            server-addr: 192.168.59.128:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.59.128:3306/seata_storage
        username: root
        password: rootroot
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
  • 在resources下创建file.confregistry.conf

    同4.4.3.1

  • 创建domain【实体类】

    • CommonResult

      同4.4.3.1

    • Storage

      package com.atguigu.springcloud.alibaba.domain;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Storage {
          private Long id;
      
          /**
           * 产品id
           */
          private Long productId;
      
          /**
           * 总库存
           */
          private Integer total;
      
          /**
           * 已用库存
           */
          private Integer used;
      
          /**
           * 剩余库存
           */
          private Integer residue;
      }
      
  • Dao接口及实现

    • StorageDao

      package com.atguigu.springcloud.alibaba.dao;
      
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      
      @Mapper
      public interface StorageDao {
      
          //扣减库存信息
          void decrease(@Param("productId") Long productId, @Param("count") Integer count);
      }
      
    • StorageMapper.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.atguigu.springcloud.alibaba.dao.StorageDao">
          <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Storage">
              <id column="id" property="id" jdbcType="BIGINT"/>
              <result column="product_id" property="productId" jdbcType="BIGINT"/>
              <result column="total" property="total" jdbcType="INTEGER"/>
              <result column="used" property="used" jdbcType="INTEGER"/>
              <result column="residue" property="residue" jdbcType="INTEGER"/>
          </resultMap>
      
          <update id="decrease">
              UPDATE
                  t_storage
              SET
                  used = used + #{count},residue = residue - #{count}
              WHERE
                  product_id = #{productId}
          </update>
      </mapper>
      
  • Service接口及实现

    • StorageService

      package com.atguigu.springcloud.alibaba.service;
      

      public interface StorageService {

      // 扣减库存
      void decrease(Long productId, Integer count);
      

      }
      ​```

    • StorageServiceImpl

      package com.atguigu.springcloud.alibaba.service.impl;
      
      import com.atguigu.springcloud.alibaba.dao.StorageDao;
      import com.atguigu.springcloud.alibaba.service.StorageService ;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      

      @Service
      public class StorageServiceImpl implements StorageService {

      private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
      
      @Resource
      private StorageDao storageDao;
      
      // 扣减库存
      @Override
      public void decrease(Long productId, Integer count) {
          LOGGER.info("------->storage-service中扣减库存开始");
          storageDao.decrease(productId,count);
          LOGGER.info("------->storage-service中扣减库存结束");
      }
      

      }
      ​```

  • Controller

    package com.atguigu.springcloud.alibaba.controller;
    

    import com.atguigu.springcloud.alibaba.domain.CommonResult ;
    import com.atguigu.springcloud.alibaba.service.StorageService ;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class StorageController {

    @Autowired
    private StorageService storageService;
    
    
    //扣减库存
    @RequestMapping("/storage/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId, count);
        return new CommonResult(200,"扣减库存成功!");
    }
    

    }

  • 配置类

    同4.4.3.1

  • 主启动

    package com.atguigu.springcloud.alibaba;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataStorageServiceApplication2002
    {
        public static void main(String[] args)
        {
            SpringApplication.run(SeataStorageServiceApplication2002.class, args);
        }
    }
    

3.3 账户微服务
  • 建Module【seata-order-service2003】

  • 改POM

    同4.4.3.1

  • 写YML

    server:
      port: 2003
    
    spring:
      application:
        name: seata-account-service
      cloud:
        alibaba:
          seata:
            #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应
            tx-service-group: fsp_tx_group
        nacos:
          discovery:
            server-addr: 192.168.59.128:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.59.128:3306/seata_account
        username: root
        password: rootroot
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    
    
  • 在resources下创建file.confregistry.conf

    同4.4.3.1

  • 创建domain【实体类】

    • CommonResult

      同4.4.3.1

    • Account

      package com.atguigu.springcloud.alibaba.domain;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      import java.math.BigDecimal;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Account {
          /**
           * id
           */
          private Long id;
      
          /**
           * 用户id
           */
          private Long userId;
      
          /**
           * 总额度
           */
          private BigDecimal total;
      
          /**
           * 已用余额
           */
          private BigDecimal used;
      
          /**
           * 剩余可用额度
           */
          private BigDecimal residue;
      }
      
  • Dao接口及实现

    • AccountDao

      package com.atguigu.springcloud.alibaba.dao;
      
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.springframework.stereotype.Component;
      import org.springframework.stereotype.Repository;
      
      import java.math.BigDecimal;
      
      @Mapper
      public interface AccountDao {
      
          /**
           * 扣减账户余额
           */
          void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
      }
      
    • AccountDao.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.atguigu.springcloud.alibaba.dao.AccountDao">
          <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Account">
              <id column="id" property="id" jdbcType="BIGINT"/>
              <result column="user_id" property="userId" jdbcType="BIGINT"/>
              <result column="total" property="total" jdbcType="DECIMAL"/>
              <result column="used" property="used" jdbcType="DECIMAL"/>
              <result column="residue" property="residue" jdbcType="DECIMAL"/>
          </resultMap>
      
          <update id="decrease">
              UPDATE t_account
              SET
                residue = residue - #{money},used = used + #{money}
              WHERE
                user_id = #{userId};
          </update>
      </mapper>
      
  • Service接口及实现

    • AccountService

      package com.atguigu.springcloud.alibaba.service;
      
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.ResponseBody;
      
      import java.math.BigDecimal;
      

      public interface AccountService {

      /**
       * 扣减账户余额
       */
      void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
      

      }
      ​```

    • AccountServiceImpl

      package com.atguigu.springcloud.alibaba.service.impl;
      

      import com.atguigu.springcloud.alibaba.dao.AccountDao;
      import com.atguigu.springcloud.alibaba.service.AccountService ;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;

      import javax.annotation.Resource;
      import java.math.BigDecimal;
      import java.util.concurrent.TimeUnit;

      /**

      • 账户业务实现类
        */
        @Service
        public class AccountServiceImpl implements AccountService {

        private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);

        @Resource
        AccountDao accountDao;

        /**

        • 扣减账户余额
          */
          @Override
          public void decrease(Long userId, BigDecimal money) {

          LOGGER.info("------->account-service中扣减账户余额开始");
          // try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
          accountDao.decrease(userId,money);
          LOGGER.info("------->account-service中扣减账户余额结束");
          }
          }
          ​```

  • Controller

    package com.atguigu.springcloud.alibaba.controller;
    
    import com.atguigu.springcloud.alibaba.domain.CommonResult ;
    import com.atguigu.springcloud.alibaba.service.AccountService ;
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    import javax.annotation.Resource;
    import java.math.BigDecimal;
    
    @RestController
    public class AccountController {
    
        @Resource
        AccountService accountService;
    
        /**
         * 扣减账户余额
         */
        @RequestMapping("/account/decrease")
        public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
            accountService.decrease(userId,money);
            return new CommonResult(200,"扣减账户余额成功!");
        }
    }
    
  • 配置类

    同4.4.3.1

  • 主启动

    package com.atguigu.springcloud.alibaba;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    

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

4.4.4 测试

启动单机版Nacos

启动Seata

启动订单、库存、账户微服务

访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,执行成功。

修改seata-order-service2003服务的AccountServiceImpl,模拟超时异常

​```java
package com.atguigu.springcloud.alibaba.service.impl;

import com.atguigu.springcloud.alibaba.dao.AccountDao;
import com.atguigu.springcloud.alibaba.service.AccountService ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**

  • 账户业务实现类
    */
    @Service
    public class AccountServiceImpl implements AccountService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);

    @Resource
    AccountDao accountDao;

    /**

    • 扣减账户余额
      */
      @Override
      public void decrease(Long userId, BigDecimal money) {

      LOGGER.info("------->account-service中扣减账户余额开始");
      // 模拟超时异常
      try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
      accountDao.decrease(userId,money);
      LOGGER.info("------->account-service中扣减账户余额结束");
      }
      }


再次访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,报错

![](https://gitee.com/honourer/picturebed/raw/master/SpringCloudAlibaba/QQ截图20240205130846.png)

![](https://gitee.com/honourer/picturebed/raw/master/SpringCloudAlibaba/QQ截图20240205131059.png)

结论:

- 当库存和账户余额扣减后,订单状态并没有设置为已经完成,没有从零改为1
- **由于feign的重试机制,账户余额还有可能被多次扣减**

修改seata-order-service2001服务的OrderServiceImpl

```java
package com.atguigu.springcloud.alibaba.service.impl;

import com.atguigu.springcloud.alibaba.dao.OrderDao;
import com.atguigu.springcloud.alibaba.domain.Order;
import com.atguigu.springcloud.alibaba.service.AccountService;
import com.atguigu.springcloud.alibaba.service.OrderService;
import com.atguigu.springcloud.alibaba.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


@Service
@Slf4j
public class OrderServiceImpl implements OrderService
{
  @Resource
  private OrderDao orderDao;
  @Resource
  private StorageService storageService;
  @Resource
  private AccountService accountService;

  /**
   * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
   */

  @Override
  @GlobalTransactional(name = "fsp_create_order", rollbackFor = Exception.class)
  public void create(Order order){
      log.info("----->开始新建订单");
      //新建订单
      orderDao.create(order);

      //扣减库存
      log.info("----->订单微服务开始调用库存,做扣减Count");
      storageService.decrease(order.getProductId(),order.getCount());
      log.info("----->订单微服务开始调用库存,做扣减end");

      //扣减账户
      log.info("----->订单微服务开始调用账户,做扣减Money");
      accountService.decrease(order.getUserId(),order.getMoney());
      log.info("----->订单微服务开始调用账户,做扣减end");


      //修改订单状态,从零到1代表已经完成
      log.info("----->修改订单状态开始");
      orderDao.update(order.getUserId(),0);
      log.info("----->修改订单状态结束");

      log.info("----->下订单结束了");

  }
}

再次访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,报错,但数据库的数据没有变化,说明正常回滚了。

至此,Seata解决分布式的跨数据库的多个微服务调用之间的全局事务控制问题的实战完成。

标签:实战,系列,Seata,atguigu,springframework,alibaba,org,import,com
From: https://www.cnblogs.com/wzzzj/p/18039388

相关文章

  • Seata系列之(三)Seata-Server安装
    Seata-Server安装此次使用的是Seata0.9.01.下载地址https://github.com/seata/seata/releases这里使用的是Linux虚拟机,所以下载的是Linux版的Linux版下载地址:https://github.com/apache/incubator-seata/releases/download/v0.9.0/seata-server-0.9.0.tar.gz2.修改file.c......
  • Seata系列之(二)Seata简介
    Seata简介SpringCloudAlibabaSeata处理分布式事务1.是什么Seata(SimpleExtensibleAutonomousTransactionArchitecture,简单可扩展自治事务框架)一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务官网:http://seata.io/zh-cn/2.Sea......
  • Seata系列之(一)分布式事务问题
    分布式事务问题用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:仓储服务:对始定的商品扣除仓储数量。订单服务:根据采购需创建订单。帐户服务∶从用户帐户中扣除余额。​ 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个......
  • Sentinel系列之(十)规则持久化
    规则持久化1.是什么在Sentinel中配置的规则在资源所在的服务重启后就消失了以cloudalibaba-sentinel-service8401为例进行说明启动单机版Nacos启动Sentinel启动cloudalibaba-sentinel-service8401为cloudalibaba-sentinel-service8401中的/rateLimit/byUrl这个接口配置Sen......
  • Sentinel系列之(九)服务熔断
    服务熔断Sentinel整合Ribbon和OpenFeign@SentinelResource的fallback1.Ribbon系列1.1服务提供者新建cloudalibaba-provider-payment9003和cloudalibaba-provider-payment90049003和9004是一样的,以9003为例建Module【cloudalibaba-provider-payment9003】改POM<?xml......
  • Sentinel系列之(八)@SentinelResource
    @SentinelResource相当于Hystrix中的@HystrixCommand1.按资源名称限流环境说明启动了单机版的Nacos启动了Sentinel基于项目cloudalibaba-sentinel-service8401继续改造增加RateLimitControllerpackagecom.atguigu.springcloud.alibaba.controller;importcom.ali......
  • Sentinel系列之(七)系统规则
    系统规则【系统自适应限流】官网:https://github.com/alibaba/Sentinel/wiki/系统自适应限流1.基本介绍从整体维度对应用入口流量进行控制【其他规则是针对接口的,系统规则是针对所有接口的】违反规则后整个系统不可用阈值类型Load自适应(仅对Linux/Unix-like机器生效):系......
  • Sentinel系列之(六)热点参数限流规则
    热点参数限流规则......
  • Sentinel系列之(五)降级规则
    降级规则官网:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html1.基本介绍​ Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。​ 当资源被降级......
  • Sentinel系列之(四)流控规则
    流控规则流量控制官网:https://github.com/alibaba/Sentinel/wiki/流量控制1.基本介绍资源名:唯一名称,默认请求路径针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)阈值类型/单机阈值:QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时......