springcloud3 分布式事务Seata-AT模式5-1

一 案例操作

1.1 背景说明

本案例的操作是在第1篇关于seta博客的基础上,后续进行操作的。

案例逻辑:这里有3个服务:一个订单服务,一个库存服务,一个账户服务

1.当用户下单时,会在订单服务中创建一个订单,

2.然后通过远程调用库存服务来扣减下单商品的库存,

3.再通过远程调用账户服务来扣减用户账户里面的余额,

4.最后在订单服务中修改订单状态为已完成。

1.2 逻辑流程图

1.3 操作案例

1.3.1 创建库,表

1.seata_order   存储订单数据库

CREATE DATABASE seata_order;
USE seata_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 CHARSET=utf8;

2.seata_storage   存储库存的数据库

CREATE DATABASE seata_storage;
USE seata_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=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);

3.seata_account:存储账户信息的数据库

CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    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=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);

4.undo.log表

在3个库中分别建立对应的回滚日志表undo_log:为各个微服务模块的数据库添加事务回滚表 undo_log ,切记用到的微服务都要创建这张表。

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
 drop table if exists `undo_log`;
CREATE TABLE `undo_log` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `branch_id` bigint(20) NOT NULL,
 `xid` varchar(100) NOT NULL,
 `context` varchar(128) NOT NULL,
 `rollback_info` longblob NOT NULL,
 `log_status` int(11) 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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

5.最后,查看所建立的表

 1.3.2 新建工程seta-order

 1.pom文件

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--feign-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- seata的依赖-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.seata</groupId>
          <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.seata</groupId>
      <artifactId>seata-spring-boot-starter</artifactId>
      <version>1.4.2</version>
    </dependency>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--  常规依赖 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-joda</artifactId>
      <version>2.9.6</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-parameter-names</artifactId>
    </dependency>
    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.2.5</version>
    </dependency>
    <!-- alibaba的druid数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.9</version>
    </dependency>
    <!-- lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

2.application配置文件

# nacos配置
server:
  port: 6001

spring:
  datasource:
    name: mysql_test
    type: com.alibaba.druid.pool.DruidDataSource
    #druid相关配置
    druid:
      #监控统计拦截的filters
      filters: stat
      driver-class-name: com.mysql.jdbc.Driver
      #基本属性
      url: jdbc:mysql://127.0.0.1:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: cloudiip
      #配置初始化大小/最小/最大
      initial-size: 1
      min-idle: 1
      max-active: 20
      #获取连接等待超时时间
      max-wait: 60000
      #间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      #一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
  application:
    name: seata-order-server
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      #config:
       # server-addr: localhost:8848 #Nacos作为配置中心地址
        #file-extension: yaml #指定yaml格式的配置
        #group: DEV_GROUP_ljf
        #namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4
    alibaba:
      seata: #设置事务分支 默认就是 my_test_tx_group
        tx-service-group: default_tx_group
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ljf.mscloud.model
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#pagehelper
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
  returnPageInfo: check
seata:
  enabled: true
  enable-auto-data-source-proxy: true
  tx-service-group: default_tx_group #此处与上面config.txt中的vgroupMapping一致
  registry:
    type: nacos
    nacos:
      application: seata-server    #服务name要和nacos中保持一致 默认就是seata-server
      server-addr: 127.0.0.1:8848  #根据自己情况调整
      username: nacos              #根据自己情况调整
      password: nacos          #根据自己情况调整
      cluster: default
      group: DEV_GROUP_ljf
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4  #根据自己情况调整
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848 #根据自己情况调整
      group: DEV_GROUP_ljf
      username: nacos       #根据自己情况调整
      password: nacos         #根据自己情况调整
      dataId: seata-server.properties
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4    #根据自己情况调整
  service:
    vgroup-mapping:
      default_tx_group: default
feign:
  hystrix:
    enabled: false

3.controller

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

}

4.service

1.接口

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

2.实现类

package com.ljf.mscloud.service.impl;

import com.ljf.mscloud.dao.OrderDao;
import com.ljf.mscloud.model.Order;
import com.ljf.mscloud.service.AccountService;
import com.ljf.mscloud.service.OrderService;
import com.ljf.mscloud.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * @ClassName: OrderServiceImpl
 * @Description: TODO
 * @Author: admin
 * @Date: 2023/03/01 19:05:06 
 * @Version: V1.0
 **/
@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("----->开始新建订单");
        //1 新建订单
        orderDao.create(order);

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

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

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

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");
    }
}

3.StorageFeginclient:

@FeignClient(value = "seata-storage-server")
public interface StorageService
{
    @PostMapping(value = "/storage/decrease")
    CommonResult decreaseStorage(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

4.AccountFeginClient

@FeignClient(value = "seata-account-server")
public interface AccountService
{
    @PostMapping(value = "/account/decrease")
    CommonResult decreaseAccount(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

5.model

1.order

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order
{
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //订单状态:0:创建中;1:已完结
}

2.commonresult

@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);
    }
}

6.mapper

1.dao

@Mapper
public interface OrderDao {
    //1 新建订单
    void create(Order order);

    //2 修改订单状态,从零改为1
    void update(@Param("userId") Long userId, @Param("status") Integer status);
}

2.mapper

<?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.ljf.mscloud.dao.OrderDao" >

	<resultMap id="BaseResultMap" type="com.ljf.mscloud.model.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>
</mapper>

 7.启动类

@EnableDiscoveryClient
@SpringBootApplication
@EnableAutoDataSourceProxy
@EnableFeignClients
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
        System.out.println("服务启动成功!!!!!!!");
    }
}

1.3.3 工程seta-storage

1.pom文件

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--feign-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- seata的依赖-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.seata</groupId>
          <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.seata</groupId>
      <artifactId>seata-spring-boot-starter</artifactId>
      <version>1.4.2</version>
    </dependency>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--  常规依赖 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-joda</artifactId>
      <version>2.9.6</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-parameter-names</artifactId>
    </dependency>
    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.2.5</version>
    </dependency>
    <!-- alibaba的druid数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.9</version>
    </dependency>
    <!-- lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

2.application配置文件

# nacos配置
server:
  port: 6002

spring:
  datasource:
    name: mysql_test
    type: com.alibaba.druid.pool.DruidDataSource
    #druid相关配置
    druid:
      #监控统计拦截的filters
      filters: stat
      driver-class-name: com.mysql.jdbc.Driver
      #基本属性
      url: jdbc:mysql://127.0.0.1:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: cloudiip
      #配置初始化大小/最小/最大
      initial-size: 1
      min-idle: 1
      max-active: 20
      #获取连接等待超时时间
      max-wait: 60000
      #间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      #一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
  application:
    name: seata-storage-server
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      #config:
       # server-addr: localhost:8848 #Nacos作为配置中心地址
        #file-extension: yaml #指定yaml格式的配置
        #group: DEV_GROUP_ljf
        #namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4
    alibaba:
      seata: #设置事务分支 默认就是 my_test_tx_group
        tx-service-group: default_tx_group
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ljf.mscloud.model
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#pagehelper
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
  returnPageInfo: check
seata:
  enabled: true
  enable-auto-data-source-proxy: true
  tx-service-group: default_tx_group #此处与上面config.txt中的vgroupMapping一致
  registry:
    type: nacos
    nacos:
      application: seata-server    #服务name要和nacos中保持一致 默认就是seata-server
      server-addr: 127.0.0.1:8848  #根据自己情况调整
      username: nacos              #根据自己情况调整
      password: nacos          #根据自己情况调整
      cluster: default
      group: DEV_GROUP_ljf
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4  #根据自己情况调整
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848 #根据自己情况调整
      group: DEV_GROUP_ljf
      username: nacos       #根据自己情况调整
      password: nacos         #根据自己情况调整
      dataId: seata-server.properties
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4    #根据自己情况调整
  service:
    vgroup-mapping:
      default_tx_group: default
feign:
  hystrix:
    enabled: false

3.controller

@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.service

public interface StorageService {
    /**
     * 扣减库存
     */
    void decrease(Long productId, Integer count);
}

5.dao

@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中扣减库存结束");
    }
}
<mapper namespace="com.ljf.mscloud.dao.StorageDao">

    <resultMap id="BaseResultMap" type="com.ljf.mscloud.model.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>

6.model

1.Storage

@Data
public class Storage {

    private Long id;

    /**
     * 产品id
     */
    private Long productId;

    /**
     * 总库存
     */
    private Integer total;

    /**
     * 已用库存
     */
    private Integer used;

    /**
     * 剩余库存
     */
    private Integer residue;
}

2.commonResult

@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);
    }
}

7.启动类

@EnableDiscoveryClient
@SpringBootApplication
@EnableAutoDataSourceProxy
@EnableFeignClients
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
        System.out.println("服务启动成功!!!!!!!");
    }
}

1.3.4 工程seta-account

1.pom文件

 <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--feign-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- seata的依赖-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.seata</groupId>
          <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.seata</groupId>
      <artifactId>seata-spring-boot-starter</artifactId>
      <version>1.4.2</version>
    </dependency>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <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>
    <!--  常规依赖 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-joda</artifactId>
      <version>2.9.6</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-parameter-names</artifactId>
    </dependency>
    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.2.5</version>
    </dependency>
    <!-- alibaba的druid数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.9</version>
    </dependency>
    <!-- lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

2.application配置文件

# nacos配置
server:
  port: 6003

spring:
  datasource:
    name: mysql_test
    type: com.alibaba.druid.pool.DruidDataSource
    #druid相关配置
    druid:
      #监控统计拦截的filters
      filters: stat
      driver-class-name: com.mysql.jdbc.Driver
      #基本属性
      url: jdbc:mysql://127.0.0.1:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: cloudiip
      #配置初始化大小/最小/最大
      initial-size: 1
      min-idle: 1
      max-active: 20
      #获取连接等待超时时间
      max-wait: 60000
      #间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      #一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
  application:
    name: seata-account-server
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      #config:
       # server-addr: localhost:8848 #Nacos作为配置中心地址
        #file-extension: yaml #指定yaml格式的配置
        #group: DEV_GROUP_ljf
        #namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4
    alibaba:
      seata: #设置事务分支 默认就是 my_test_tx_group
        tx-service-group: default_tx_group
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ljf.mscloud.model
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#pagehelper
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
  returnPageInfo: check
seata:
  enabled: true
  enable-auto-data-source-proxy: true
  tx-service-group: default_tx_group #此处与上面config.txt中的vgroupMapping一致
  registry:
    type: nacos
    nacos:
      application: seata-server    #服务name要和nacos中保持一致 默认就是seata-server
      server-addr: 127.0.0.1:8848  #根据自己情况调整
      username: nacos              #根据自己情况调整
      password: nacos          #根据自己情况调整
      cluster: default
      group: DEV_GROUP_ljf
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4  #根据自己情况调整
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848 #根据自己情况调整
      group: DEV_GROUP_ljf
      username: nacos       #根据自己情况调整
      password: nacos         #根据自己情况调整
      dataId: seata-server.properties
      namespace: 05573840-fcf3-472d-a64a-c66b4fe878f4    #根据自己情况调整
  service:
    vgroup-mapping:
      default_tx_group: default
feign:
  hystrix:
    enabled: false

3.controller

@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.service

public interface AccountService {

    /**
     * 扣减账户余额
     * @param userId 用户id
     * @param money 金额
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
/**
 * 账户业务实现类
 * Created by zzyy on 2019/11/11.
 */
@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中扣减账户余额结束");
    }
}

5..dao

@Mapper
public interface AccountDao {

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

6.model

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 总额度
     */
    private BigDecimal total;

    /**
     * 已用额度
     */
    private BigDecimal used;

    /**
     * 剩余额度
     */
    private BigDecimal residue;
}
@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);
    }
}

7.启动类

@EnableDiscoveryClient
@SpringBootApplication
@EnableAutoDataSourceProxy
@EnableFeignClients
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class, args);
        System.out.println("服务启动成功!!!!!!!");
    }
}

1.4  测试环节

1.4.1 服务启动准备阶段

需求先启动nacos,seata这个两个服务,然后启动工程seata-order,seata-storage,seata-account这3个工程。

1.4.2正常访问阶段

1.查看3张表

2请求访问后 

http://localhost:6001/order/create?userId=001&productId=001&count=1&money=400&status=0

访问后,再次观察3张表,均按照:下订单->扣库存->减余额->改状态 流程执行正确无误。

 

 1.4.3 超时异常,没有加@GlobalTransaction

 1.在账户模块中,模拟超时,延迟20s,再次启动账户模块

 2.再次访问,页面报错

 3.查看这3张表,发现库存表实现了减库存,账户实现了减余额操作,新订单表中也新增了1条订单数据,但是订单状态还是0,订单状态没有完成改变。

 4. 原因在于:调用账户模块超时,导致订单模块发生异常,无法进行最后一步修改订单状态逻辑的执行。

  1.4.4 超时异常,添加@GlobalTransaction

1.在订单模块中,执行方法新增 @GlobalTransaction注解,重启order服务

2.多次刷新后,虽然前端还是报错,只是显示问题不管

http://localhost:6001/order/create?userId=001&productId=001&count=1&money=400&status=0

3.可以查看订单表,库存表,账户表均没有发生改变,说明添加@GlobalTransaction

已经启动作用,分布式事务已经起作用。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值