天天看點

SpringCloud Alibaba 九(Seata介紹二)

Seata 案例

我們會在項目中建立三個服務,倉儲服務,訂單服務,帳戶服務。當使用者下單時,會在訂單服務中建立一個訂單,然後通過遠端調用倉儲服務來扣減下單商品的庫存,再通過遠端調用賬戶服務來扣減使用者賬戶裡面的餘額。該操作跨越三個資料庫,有兩次遠端調用,很明顯會有分布式事務問題。

架構圖
SpringCloud Alibaba 九(Seata介紹二)

1、我們需要新增三個資料庫 seata_order、seata_account、seata_store 。

SpringCloud Alibaba 九(Seata介紹二)

2、在三個庫中建立表:

在seata_order庫中建立order 表::

DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  `status` INT(1) DEFAULT NULL COMMENT '訂單狀态:0:建立中;1:已完結' ,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
           

在seata_account庫中建立account表:

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `seata_account`.`account`(`id`, `user_id`, `money`) VALUES (1, '1', 1000);
           

在seata_store庫中新增 storage表

DROP TABLE IF EXISTS `storage`;
CREATE TABLE `storage` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `seata_store`.`storage`(`id`, `commodity_code`, `count`) VALUES (1, '1', 100);

           

因為SEATA AT 模式需要 UNDO_LOG 表,這個需要在每一個業務庫中建立表:

DROP TABLE IF EXISTS `undo_log`;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
           
SpringCloud Alibaba 九(Seata介紹二)

建立Module pcloud-alibaba-seata-order-service9001

SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)

修改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>pcloud-alibaba</artifactId>
        <groupId>com.younger.pcloud.alibaba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>pcloud-alibaba-seata-order-service9001</artifactId>

    <name>pcloud-alibaba-seata-order-service9001</name>

    <dependencies>
        <!--nacos-->
        <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>
            <!--排除項目自帶的包-->
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <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>

</project>

           

新增 application.yml檔案:

server:
  port: 9001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定義事務組名稱需要與seata-server中的對應
        tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: mysql

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml


           
SpringCloud Alibaba 九(Seata介紹二)

目錄結構:

SpringCloud Alibaba 九(Seata介紹二)

新增registry.conf檔案:

registry.conf:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    timeout = "0"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
  custom {
    name = ""
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seata.properties"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
    aclToken = ""
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
    nodePath = "/seata/seata.properties"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
  custom {
    name = ""
  }
}
           

這個registry.conf配置檔案可以拷貝,我們上一節配置的registry.conf檔案。

新增啟動類: SeataOrderMain9001

package com.younger.pcloud.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;


@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableAutoDataSourceProxy
public class SeataOrderMain9001 {

    public static void main(String[] args) {
        SpringApplication.run(SeataOrderMain9001.class, args);
    }
}

           

新增domain包:

新增order類:

package com.younger.pcloud.alibaba.domain;

public class Order {

    private Long id;

    private String userId;

    private String commodityCode;

    private Integer count;

    private Integer money;

    /**
     * 訂單狀态:0:建立中;1:已完結
     */
    private Integer status;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getCommodityCode() {
        return commodityCode;
    }

    public void setCommodityCode(String commodityCode) {
        this.commodityCode = commodityCode;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Integer getMoney() {
        return money;
    }

    public void setMoney(Integer money) {
        this.money = money;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

           

新增 Result類:

package com.younger.pcloud.alibaba.domain;

public class Result<T> {

    private Integer code;
    private String  message;
    private T       data;
	
	public Result() {
       
    }

    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(Integer code, String message) {
        this(code,message,null);
    }
	public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

           

新增config 包:

新增 MyBatisConfig類:

package com.younger.pcloud.alibaba.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"com.younger.pcloud.alibaba.dao"})
public class MyBatisConfig {


}

           

新增dao 包:

新增 OrderDao 類:

package com.younger.pcloud.alibaba.dao;

import com.younger.pcloud.alibaba.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface OrderDao {

    /**
     * 建立訂單
     * @param order
     */
    void createOrder(Order order);


    /**
     * 修改訂單狀态
     */
    void update(@Param("userId") String userId, @Param("status") Integer status);
}

           

新增 mapper 包:

新增 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.younger.pcloud.alibaba.dao.OrderDao">
    <insert id="createOrder">
        INSERT INTO `order` (`id`, `user_id`, `commodity_code`, `count`, `money`,`status`)
        VALUES (NULL, #{userId}, #{commodityCode}, #{count}, #{money},0);
    </insert>

    <update id="update">
        UPDATE `order`
        SET status = 1
        WHERE user_id = #{userId} AND status = #{status};
    </update>
</mapper>


           

新增service包:

新增 OrderService類:

package com.younger.pcloud.alibaba.service;

import com.younger.pcloud.alibaba.domain.Order;

public interface OrderService {

    /**
     * 建立訂單
     */
    void createOrder(Order order);

}

           

新增fegin遠端調用包:

新增 AccountService類:

package com.younger.pcloud.alibaba.service.fegin;

import com.younger.pcloud.alibaba.domain.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "seata-account-service")
public interface AccountService {

    /**
     * 扣減賬戶餘額
     * @param userId
     * @param money
     * @return
     */
    @PostMapping("/account/decrease")
    Result decrease(@RequestParam("userId") String userId, @RequestParam("money") Integer money);
}

           

新增StorageService類:

package com.younger.pcloud.alibaba.service.fegin;

import com.younger.pcloud.alibaba.domain.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "seata-store-service")
public interface StorageService {

    /**
     * 扣減庫存
     */
    @PostMapping(value = "/storage/decrease")
    Result decrease(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);

}

           

新增OrderService 實作類OrderServiceImpl :

package com.younger.pcloud.alibaba.service.impl;

import com.younger.pcloud.alibaba.dao.OrderDao;
import com.younger.pcloud.alibaba.domain.Order;
import com.younger.pcloud.alibaba.service.OrderService;
import com.younger.pcloud.alibaba.service.fegin.AccountService;
import com.younger.pcloud.alibaba.service.fegin.StorageService;
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 createOrder(Order order) {

        log.info("開始建立訂單....");
        //本應用建立訂單
        orderDao.createOrder(order);

        //遠端調用庫存服務扣減庫存
        log.info("order-service------扣減庫存開始");
        storageService.decrease(order.getCommodityCode(),order.getCount());
        log.info("order-service------扣減庫存結束");

        //遠端調用賬戶服務扣減餘額
        log.info("order-service-------中扣減使用者餘額開始");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("order-service-------中扣減餘額結束");

        //修改訂單狀态為已完成
        log.info("order-service-------中修改訂單狀态開始");
        orderDao.update(order.getUserId(),0);
        log.info("order-service-------中修改訂單狀态結束");

        log.info("訂單完成!!!!!");
    }
}

           

新增Controller 包:

新增 OrderController 類:

package com.younger.pcloud.alibaba.controller;

import com.younger.pcloud.alibaba.domain.Order;
import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.OrderService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class OrderController {

    @Resource
    private OrderService orderService;

    @PostMapping("/order/create")
    public Result createOrder(@RequestBody Order order) {
        orderService.createOrder(order);
        return new Result(200,"訂單建立成功");
    }
}

           
建立modul pcloud-alibaba-seata-store-service9002

目錄結構:

SpringCloud Alibaba 九(Seata介紹二)

修改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>pcloud-alibaba</artifactId>
    <groupId>com.younger.pcloud.alibaba</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>pcloud-alibaba-seata-store-service9002</artifactId>

  <dependencies>
    <!--nacos-->
    <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>
      <exclusions>
        <exclusion>
          <artifactId>seata-all</artifactId>
          <groupId>io.seata</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.seata</groupId>
      <artifactId>seata-all</artifactId>
      <version>1.4.2</version>
    </dependency>
    <!--feign-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</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>2.0.0</version>
    </dependency>
    <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.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

</project>

           

新增application.yml檔案:

server:
  port: 9002

spring:
  application:
    name: seata-store-service
  cloud:
    alibaba:
      seata:
        #自定義事務組名稱需要與seata-server中的對應
        tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_store
    username: root
    password: mysql

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml


           

拷貝 file.conf 和 registry.conf 檔案到Resource目錄下:

新增啟動類 SeataStorageServiceMain9002:

package com.younger.pcloud.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)
@EnableAutoDataSourceProxy
public class SeataStorageServiceMain9002 {

    public static void main(String[] args) {

        SpringApplication.run(SeataStorageServiceMain9002.class ,args);
    }
}
           

從9001 拷貝 config 目錄和registry.conf 到 9002

新增domain 目錄:

新增Result 類:

package com.younger.pcloud.alibaba.domain;

public class Result<T> {

    private Integer code;
    private String  message;
    private T       data;

	 public Result() {
       
     }

    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(Integer code, String message) {
        this(code,message,null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

           

新增 Store 類:

package com.younger.pcloud.alibaba.domain;

public class Store {

    private Long id;

    private String commodityCode;

    private Integer count;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCommodityCode() {
        return commodityCode;
    }

    public void setCommodityCode(String commodityCode) {
        this.commodityCode = commodityCode;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }
}

           

新增dao包:

新增 StoreDao 類:

package com.younger.pcloud.alibaba.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface StoreDao {


    /**
     * 扣減庫存
     */
    void decrease(@Param("commodityCode") String commodityCode, @Param("count") Integer count);

}

           

新增 StoreMapper.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.younger.pcloud.alibaba.dao.StoreDao">

    <update id="decrease">
        UPDATE storage
        SET count    = count - #{count}
        WHERE commodity_code = #{commodityCode}
    </update>
</mapper>
           

新增 service 包:

新增 StoreService類:

package com.younger.pcloud.alibaba.service;

public interface StoreService {
    /**
     * 扣減庫存
     */
    void decrease(String commodityCode, Integer count);
}

           

新增 StoreService實作類 StoreServiceImpl類:

package com.younger.pcloud.alibaba.service.impl;

import com.younger.pcloud.alibaba.dao.StoreDao;
import com.younger.pcloud.alibaba.service.StoreService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class StoreServiceImpl implements StoreService {

    @Resource
    private StoreDao storeDao;

    @Override
    public void decrease(String commodityCode, Integer count) {
        log.info("storage-service-------中扣減庫存開始");
        storeDao.decrease(commodityCode,count);
        log.info("storage-service--------中扣減庫存結束");

    }
}

           

新增controller 包:

新增 StoreController 類:

package com.younger.pcloud.alibaba.controller;

import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.StoreService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class StoreController {

    @Resource
    private StoreService storeService;

    /**
     * 扣減庫存
     */
   @RequestMapping("/storage/decrease")
   public Result decrease(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count) {
        storeService.decrease(commodityCode, count);
        return new Result(200,"庫存扣減成功!");
    }

}

           
建立modul pcloud-alibaba-seata-account-service9003

修改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>pcloud-alibaba</artifactId>
        <groupId>com.younger.pcloud.alibaba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>pcloud-alibaba-seata-account-service9003</artifactId>

    <name>pcloud-alibaba-seata-account-service9003</name>

    <dependencies>
        <!--nacos-->
        <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>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>2.0.0</version>
        </dependency>
        <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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

           

新增 application.yml 檔案:

server:
  port: 9003

spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      seata:
        #自定義事務組名稱需要與seata-server中的對應
        tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account
    username: root
    password: mysql

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml


           

新增啟動類SeataAccountServiceMain9003:

package com.younger.pcloud.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)
@EnableAutoDataSourceProxy
public class SeataAccountServiceMain9003 {

    public static void main(String[] args) {
        SpringApplication.run(SeataAccountServiceMain9003.class,args);
    }
}

           

目錄結構:

SpringCloud Alibaba 九(Seata介紹二)

從9001 拷貝 config 目錄和registry.conf 到 9003

新增domain 包:

新增 Account 類:

package com.younger.pcloud.alibaba.domain;

public class Account {

    private Long id;

    private String userId;

    private Integer money;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public Integer getMoney() {
        return money;
    }

    public void setMoney(Integer money) {
        this.money = money;
    }
}

           

新增 Result 類:

package com.younger.pcloud.alibaba.domain;

public class Result<T> {

    private Integer code;
    private String  message;
    private T       data;
 	 public Result() {
       
    }
    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(Integer code, String message) {
        this(code,message,null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
           

新增dao包:

新增 AccountDao 類:

package com.younger.pcloud.alibaba.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface AccountDao {

    /**
     * 扣減賬戶餘額
     */
    void decrease(@Param("userId") String userId, @Param("money") Integer money);

}

           

新增 AccountMapper.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.younger.pcloud.alibaba.dao.AccountDao">
    <update id="decrease">
        UPDATE account
        SET
          money = money - #{money}
        WHERE
          user_id = #{userId};
    </update>
</mapper>
           

新增service 包:

新增 AccountService 類:

package com.younger.pcloud.alibaba.service;

import org.springframework.web.bind.annotation.RequestParam;

public interface AccountService {
    /**
     * 扣減賬戶餘額
     * @param userId 使用者id
     * @param money 金額
     */
    void decrease(@RequestParam("userId") String  userId, @RequestParam("money") Integer money);

}

           

新增 AccountService 的實作類AccountServiceImpl:

package com.younger.pcloud.alibaba.service.impl;

import com.younger.pcloud.alibaba.dao.AccountDao;
import com.younger.pcloud.alibaba.service.AccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


@Service
@Slf4j
public class AccountServiceImpl implements AccountService {

    @Resource
    AccountDao accountDao;

    /**
     * 扣減賬戶餘額
     */
    @Override
    public void decrease(String userId, Integer money) {
        log.info("account-service--------中扣減賬戶餘額開始");
        accountDao.decrease(userId,money);
        log.info("account-service-------中扣減賬戶餘額結束");
    }
}
           

新增 controller包:

新增 AccountController 類:

package com.younger.pcloud.alibaba.controller;

import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.AccountService;
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;

@RestController
public class AccountController {

    @Resource
    private AccountService accountService;

    /**
     * 扣減賬戶餘額
     */
    @RequestMapping("/account/decrease")
    public Result decrease(@RequestParam("userId") String userId, @RequestParam("money") Integer money){
        accountService.decrease(userId,money);
        return new Result(200,"賬戶餘額扣減成功!");
    }
}

           

啟動項目測試:

SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)

通過日志,我們可以知道我們的事務已經注冊成功了。

建立訂單: http://localhost:9001/order/create

參數請求:

{
    "id":1,
    "userId":"1",
    "commodityCode":"1",
    "count":1,
    "money":55
}
           
SpringCloud Alibaba 九(Seata介紹二)

我們服務之間的調用已經成功了。

我們看下表的資料:

SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)

表裡面的資料和我們預想的結果是一樣的。

我們現在沒有在我們的業務上面添加事務,我們測試一下逾時會怎麼樣?

我們修改我們的AccountServiceImpl類:

SpringCloud Alibaba 九(Seata介紹二)

可以看到我們在類上面添加了逾時異常。

我們重新開機下項目測試:

測試:

SpringCloud Alibaba 九(Seata介紹二)

此刻通路已經逾時了。

我們看下資料庫:

SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)

可以看到,我們在已經建立了訂單,并且庫存已經抵扣了,但是使用者沒有支付成功。這個就是我們常見的分布式事務的問題。

我們在我們的業務類上面添加我們的分布式注解:@GlobalTransactional

我們修改 OrderServiceImpl類:

SpringCloud Alibaba 九(Seata介紹二)

隻需要在我們的業務類上添加一個 @GlobalTransactional注解即可,這個name是我們自己定義的,但是需要全局唯一。

我們重新啟動項目看下:

測試:

SpringCloud Alibaba 九(Seata介紹二)

執行也是逾時的,我們在看下資料庫:

SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)
SpringCloud Alibaba 九(Seata介紹二)

我們看到訂單和庫存都沒有記錄。說明我們的分布式事務已經成功了。

總結一下分布式事務的執行流程:

1、TM 開啟分布式事務(TM 向 TC 注冊全局事務記錄)。

2、RM 向 TC 彙報資源準備狀态。

3、TM 通知 TC 送出/復原分布式事務。

4、TC 彙總事務資訊,決定分布式事務是送出還是復原。

5、TC 通知所有 RM 送出/復原 資源,事務二階段結束。