菜鸟的Spring Cloud Alibaba学习总结(三):Seata

说明

更新时间:2020/10/10 02:41,更新到了TC、TM、RM

本文主要对Spring Cloud Alibaba中的Seata进行学习与记录,偏向于实战,简单讲一下下面用到技术及版本,docker部署Seata1.3.0,Mysql采用8.0及以上,nacos采用1.3.1,重点讲一下Seata部署的方式,因为1.30版本跟0.9版本差距很大,代码不会过于详细,本文会持续更新,不断地扩充

注意:本文仅为记录学习轨迹,如有侵权,联系删除

一、概述

这里给出官网地址:http://seata.io/zh-cn/docs/overview/what-is-seata.html
Seata是什么
引用官网的话 ,Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

解决的问题
我们知道在普通的单体应用中,服务层里面的事务控制可以用@Transactional注解来实现事务的控制,但是如果是分布式的情况下,这个注解就不管用了,因为它们根本不在同一个服务中,这个时候就可以用Seata这个框架,就像官网所说的,致力于提供高性能和简单易用的分布式事务服务。

术语表
在这里插入图片描述
核心开发
关于其他的详细信息可以去官网直接看,这里重点讲一下开发,Seata的开发主要包括服务端和客户端的开发。下面直接进入实战,简单说一下技术选择和版本号

技术点版本
docker/
Seata1.3.0
Mysql8.0及以上
nacos1.3.2

二、Seata服务端开发

(1)前期准备

先去官网下载Seata1.3.0版,在进行docker部署之前,先了解一下Seata1.3.0版本的相关知识。

下载地址:http://seata.io/zh-cn/blog/download.html
在这里插入图片描述
如果下不了可以评论区留言,下载压缩包后解压即可,在conf里面有两个十分重要的文件,file.cof和registry.cof,怎么用后面会细讲
在这里插入图片描述

(2)数据库准备

先是数据库的准备,这里用的mysql,,点开上图的README-zh.md,里面有链接,可以下载SQL
在这里插入图片描述
浏览器进入链接后,进入db,复制里面mysql的语句
在这里插入图片描述

里面的sql语句这里给大家复制出来了

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

创建名为seata的数据库,再执行上面的sql语句
在这里插入图片描述
数据库创建成功

(3)Nacos服务中心的注册

这里说一下,这是一个不可少的步骤,需要将Seata的相关配置注册到Nacos服务中心,Nacos的创建和部署这里就不再重复,感兴趣的可以看一下本人之前的博客,地址:菜鸟的Spring Cloud Alibaba学习总结(一):Nacos

首先需要有配置文件,这样才能注册到Nacos中,还是上面的README-zh.md文件,现在就知道了为什么要先下载Seata的目的了吧,很多的配置文件都可以从里面的README.md获取到。
在这里插入图片描述
将里面的config.txt文件拷贝下来,放在seata安装目录的同级目录下
在这里插入图片描述
修改config.txt里面的内容,根据需要进行修改,这里给出我的配置

service.vgroupMapping.order_tx_group=default
service.default.grouplist=39.96.22.34:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://39.96.22.34:3306/seata?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

下面对这些参数进行简单的讲解,具体可以看官网的配置说明:http://seata.io/zh-cn/docs/user/configurations.html
在这里插入图片描述
配置完之后需要从上面拷贝一个脚本下来,用该脚本将配置注册到nacos中
在这里插入图片描述
将nacos-config.sh放到seata安装目录,bin同级的目录下
在这里插入图片描述
先启动nacos之后,在该目录下右键,点击Git Bush Here,这是git工具的功能,通过git进行配置的注册
在这里插入图片描述
之后执行命令:sh nacos-config.sh -h nacos部署的ip,默认采用nacos端口号为8848
在这里插入图片描述

因为我部署的nacos端口号是8080的一个集群,通过8080端口号进行转发,所以修改了脚本的端口号8080,如果需要修改端口号可以进该脚本nacos-config.sh,修改里面的端口号为自己对应端口号即可
在这里插入图片描述
注册成功后,可以登录nacos查看配置文件,会生成配置文件conf.txt对应的配置
在这里插入图片描述
ok,目前已经成功了一半,下面开始用docker部署Seata

(5)docker部署Seata

镜像的拉取:docker pull seataio/seata-server:1.3.0

挂载文件的创建,位置可以自己定义,这里给出我的文件创建,创建seata文件夹,里面创建seata-config文件夹,里面创建file.conf和registry.conf两个文件
在这里插入图片描述
编辑file.conf


## transaction log store, only used in seata-server
service {
  #transaction service group mapping
  #指定测试的事务组名称
  vgroup_mapping.order_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  #指定默认组 地址和端口,可以设置多个地址
  default.grouplist = "39.96.22.34:8091"
  #disable seata
  #禁用全局事务=false 即开启服务
  disableGlobalTransaction = false
}


store {
  ## store mode: file、db、redis
  mode = "db"


  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    url = "jdbc:mysql://39.96.22.34:3306/seata?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"
    user = "root"
    password = "123456"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

}

注意,里面的配置可以参考我们下载的seata里面的conf目录里面的file.conf,里面还有file,redis模式的配置,由于这里mode=db,选择了db数据库模式,所以file模式和redis模式就不会生效了,所以这里就删掉了,需要的时候可以再配,里面的所有配置都跟前面的conf.txt文件里面的配置一致。
在这里插入图片描述
编辑registry.conf

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

  nacos {
    # 服务名,自定义,建议默认seata-server
    application = "seata-server"
    # nacos的部署地址
    serverAddr = "39.96.22.34:8080"
    # nacos的部署地址的分组
    group = "SEATA_GROUP"
    # nacos的部署地址的命名空间
    namespace = ""
    # nacos的部署地址节点默认
    cluster = "default"
    # nacos的用户密码
    username = "nacos"
    password = "nacos"
  }
 
  
  file {
    name = "/root/seata-config/file.conf"
  }
}

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

  nacos {
  	# nacos的部署地址
    serverAddr = "39.96.22.34:8080"
    # nacos的部署地址的命名空间
    namespace = ""
    # nacos的部署地址的分组
    group = "SEATA_GROUP"
    # nacos的用户和密码
    username = "nacos"
    password = "nacos"
  }
 
  
  file {
    name = "/root/seata-config/file.conf"
  }
}

配置可以参考我们下载的seata里面的conf目录里面的registry.conf,里面还有zk,consul等模式的配置,由于这里registry的type=nacos,选择了nacos的配置,config的type=nacos,选择了nacos的配置,所以其他的consul等配置就不会生效了,所以这里就删掉了,需要的时候可以再配。

启动容器

docker run --name seata01 -d  -p 8091:8091 -e SEATA_CONFIG_NAME=file:/root/seata/config/registry -e SEATA_IP=39.96.22.34 -v /home/cainiao/seata/seata-config/:/root/seata/config --net=bridge --restart=always docker.io/seataio/seata-server:1.3.0

注意:-v后面的挂载文件的路径要根据自己的挂载文件的路径来

在这里插入图片描述
登录nacos,根据挂载的配置文件所配置的,生成了对应的服务
在这里插入图片描述
到此为止,Seata的服务端部署结束

三、Seata客户端开发

这里简单说一下下面要做的事情,开发3个微服务,订单服务、库存服务和账户服务,调用的逻辑为下单后,库存减少、账户扣款、下单结束,这个过程需要调用这3个服务,并且需要事务的控制,下面的客户端开发只讲一些重点的配置

(1)数据库准备

每一个服务都需要一个独立的数据库,分别为seata_account、seata_order和seata_storage
在这里插入图片描述
之后在对应数据库表创建对应表,完整的sql如下

-- 创建3个数据库
create database seata_order;

create database seata_storage;

create database seata_account;


-- 订单数据库表创建
use seata_order;
create table t_order (
`id` bigint(20) not null auto_increment primary key,
`user_id` bigint(20) default null comment '用户ID',
`product_id` bigint(20) default null comment '产品ID',
`count` int(11) default null comment '数量',
`money` decimal(18,2) default null comment '金额',
`status` int(1) default null comment '订单状态:0-创建中,1-已完结'
) engine=INNODB auto_increment = 1 default charset = 'utf8';

select * from t_order;

-- 库存数据库表创建
use seata_storage;
create table t_storage (
`id` bigint(20) not null auto_increment primary key,
`product_id` bigint(20) 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 = 1 default charset = 'utf8';

insert into t_storage(`id`, `product_id`, `total`, `used`, `residue`) values('1', '1', '100' , '0', '100');

select * from t_storage;

-- 账户数据库表创建
use seata_account;
create table t_account (
`id` bigint(20) not null auto_increment primary key,
`user_id` bigint(20) default null comment '用户ID',
`total` decimal(18,2) default null comment '总额度',
`used` decimal(18,2) default null comment '使用额度',
`residue` decimal(18,2) default '0' null comment '剩余额度'
) engine=INNODB auto_increment = 1 default charset = 'utf8';

insert into t_account(`id`, `user_id`, `total`, `used`, `residue`) values('1', '1', '10000' , '0', '10000');
select * from t_account;

事务回滚表(暂且这样叫吧)undo_log的添加,上面的3个数据库表都要创建这样的一张表来支持seata的事务

-- 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(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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 = utf8 COMMENT ='AT transaction mode undo table';

这张表也可以在README-zh.md里面的链接找到
在这里插入图片描述
创建完的数据库
在这里插入图片描述

(2)微服务的创建

这里一共要创建3个服务,订单、库存和账户,这里以订单模块seata-order-service2001为例,接着之前的==菜鸟的Spring Cloud Alibaba学习总结(二)==的项目继续写

pom
引入坐标依赖pom

    <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>
            <!-- 因为兼容版本问题,所以需要剔除它自己的seata的包 -->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <!--引入我们使用的自己的seata对应的版本的依赖,而不是使用starter默认的版本-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <!--alibaba druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

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

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

        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--hutool 测试雪花算法-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-captcha</artifactId>
            <version>5.2.0</version>
        </dependency>
    </dependencies>

配置文件
创建配置文件,这里要创建file.conf、registry.conf和application.yml3个配置文件,首先是file.conf


transport {
  # tcp, unix-domain-socket
  type = "TCP"
  #NIO, NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  vgroupMapping.order_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "39.96.22.34:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
    sagaBranchRegisterEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
    defaultGlobalTransactionTimeout = 60000
    degradeCheck = false
    degradeCheckPeriod = 2000
    degradeCheckAllowTimes = 10
  }
  undo {
    dataValidation = true
    onlyCareUpdateColumns = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

配置跟前面服务端讲的file.conf要保持一致,像vgroupMapping.order_tx_group、nacos部署的地址,分组等信息一定要一致,然后是registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "39.96.22.34:8080"
    group = "SEATA_GROUP"
    namespace = ""
    username = "nacos"
    password = "nacos"
  }
  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"
  }
  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"
  }
}

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

  nacos {
    serverAddr = "39.96.22.34:8080"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  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 = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

这里的配置也是跟前面的registry.conf保持一致,只是这里一些没用的模式,像zk、consul模式等这里没有删掉,前面的删掉了,这里删不删都可以,最后是application.yml

server:
  port: 2001

spring:
  application:
    name: seata-order-service


  cloud:
    alibaba:
      seata:
        # 自定义事务组名称需要与seata-server中的对应,我们之前在seata的配置文件中配置的名字
        tx-service-group: order_tx_group
    nacos:
      discovery:
        server-addr: 39.96.22.34:8080
#        namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769

  datasource:
    # 当前数据源操作类型
    type: com.alibaba.druid.pool.DruidDataSource
    # mysql驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://39.96.22.34:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456



logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath*:mapper/*.xml

主启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMain2003 {
    public static void main(String[] args) {
        SpringApplication.run(SeataAccountMain2003.class,args);
    }
}

config包
数据源配置类DataSourceProxyConfig

@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 druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSourceProxy);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources(mapperLocations));
        return bean.getObject();
    }
}

mybatis配置类MyBatisConfig

@Configuration
@MapperScan({"com.zsc.mapper"})
public class MyBatisConfig {


}

entity包

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    /**
     * 主键
     */
    private Long id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 产品id
     */
    private Long productId;
    /**
     * 数量
     */
    private Integer count;
    /**
     * 金额
     */
    private BigDecimal money;
    /**
     * 订单状态:0:创建中;1:已创建
     */
    private Integer status;
}

mapper包

@Mapper
@Repository
public interface OrderMapper {
    /**
     * 1 新建订单
     * @param order
     * @return
     */
    int create(Order order);

    /**
     * 2 修改订单状态,从0改为1
     * @param userId
     * @param status
     * @return
     */
    int update(@Param("userId") Long userId, @Param("status") Integer status);
}

对应的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.zsc.mapper.OrderMapper">

    <resultMap id="BaseResultMap" type="com.zsc.entity.Order">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="user_id" property="userId" jdbcType="BIGINT"></result>
        <result column="product_id" property="productId" jdbcType="BIGINT"></result>
        <result column="count" property="count" jdbcType="INTEGER"></result>
        <result column="money" property="money" jdbcType="DECIMAL"></result>
        <result column="status" property="status" jdbcType="INTEGER"></result>
    </resultMap>


    <insert id="create" parameterType="com.zsc.entity.Order" useGeneratedKeys="true"
            keyProperty="id">
        insert into t_order(user_id,product_id,count,money,status) values (#{userId},#{productId},#{count},#{money},0);
    </insert>

        <update id="update">
            update t_order set status =1 where user_id =#{userId} and status=#{status};
       </update>
</mapper>

server包
接口

public interface OrderServer {
    /**
     * 创建订单
     * @param order
     */
    String create(Order order);
}

利用openfeign调用其他服务的接口

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

    @RequestMapping("/Account/{id}")
    public String update(@PathVariable("id") Long userId, @RequestParam("used") BigDecimal used);

}
@FeignClient(value = "seata-storage-service")
public interface StorageServer {

    @RequestMapping("/Storage/{id}")
    public String update(@PathVariable("id") Long productId, @RequestParam("count") Integer count);
}

OrderServer的实现类

@Service
@Slf4j
public class OrderServerImpl implements OrderServer {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private AccountServer accountServer;
    @Autowired
    private StorageServer storageServer;

    @Override
    @GlobalTransactional(name = "order_tx_group",rollbackFor = Exception.class)
    public String create(Order order) {
        log.info("用户开始下订单......");
        orderMapper.create(order);

        log.info("开始更新库存......");
        String result01 = storageServer.update(order.getProductId(), order.getCount());
        log.info("库存更新成功......");

        int a = 10/0;

        log.info("开始更新用户账户......");
        String result02 = accountServer.update(order.getUserId(), order.getMoney());
        log.info("账户更新结束......");


        int result03 = orderMapper.update(order.getUserId(), 0);
        log.info("订单操作结束......");

        return "用户下单成功";
    }
}

注意:上面加了@GlobalTransactional注解,order_tx_group自定义全局事务组,表示里面的所有数据库操作要么都成功,要么都失败,而且里面可以捕捉Exception,发生异常后事务回滚

controller包

@RestController
public class OrderController {
    @Autowired
    private OrderServer orderServer;


    @GetMapping("/order")
    public String create(Order order){
        String result = orderServer.create(order);
        return result;
    }


}

还有其他两个模块这里就不细说了,配置文件file.confregistry.conf一样的,application.yml基本一致

测试,加了@GlobalTransactional,并且在里面故意加了10/0的异常,发现事务回滚了,没加的事务没有回滚
在这里插入图片描述

四、TC、TM、RM

TC (Transaction Coordinator) - 事务协调者维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

用上面的3个微服务来讲就是,RM表示里面涉及到的一个个数据库操作,TM表示@GlobalTransactional所加的那个方法,该方法里面就有一个个数据库操作,TC表示协调这些包括回滚事务的协调者
在这里插入图片描述

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值