Seata基本使用

Seata基本知识

本地锁:本地数据库在提交事务之前需要获取到本地锁和全局锁才能够提交

全局锁:在进行分布式事务时,全局事务的提交需要获取到全局锁,然后在各个本地数据库在通过获取各自数据库的本地锁实现事务提交

AT模式

AT模式分为两个阶段

  • 一阶段:解析SQL,获取本地锁,将更新前的数据保存到undo_log表中俗称before images,将更新后的数据保存到undo_log表中俗称after image,然后获取到全局锁,提交本地事务本地锁释放,获取不到全局锁不能够提交事务
  • 二阶段:二阶段分为成功和失败,成功时提交全局事务,释放全局锁,失败时,根据保存的after image和当前纪录进行对比,如果相同则进行全局事务回滚,如果不同(表示被其他操作修改过数据了)则转人工处理

专业术语

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。


Seata环境搭建

本次使用的Seata版本选用1.5.1,在该版本中没有了相应的数据库执行文件、file.conf、registry.conf等文件

seata1.5.1下载地址

其他版本下载地址,进入之后选择相应的版本然后点击Downloads即可,会跳转到对应的下载界面,看看是需要下载zip版本还是tar版本

1.Seata配置文件修改

修改confi目录下的application.yaml文件,其余配置按照原先的配置即可,需要修改的地方主要是seata处

server:
  port: 7091 

spring:
  application:
    name: seata-server


logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

seata:
  config:
    type: nacos #使用nacos作为配置中心
    nacos:
      server-addr: 192.168.72.130:8848 #启动可能会出现Client not connected,current status:STARTING,那就将地址换成http://192.168.72.130:8848
      namespace: public #命名空间配置
      group: DEFAULT_GROUP #分组信息配置
      username: nacos #如果开启了nacos的验证功能则需要配置
      password: nacos
      data-id: seataServer.properties #nacos中的配置文件名称
  registry:
    type: nacos #使用nacos作为注册中心
    nacos:
      application: seata-server
      server-addr: 192.168.72.130:8848 #出现Client not connected,current status:STARTING就配上http
      group: DEFAULT_GROUP
      namespace: public
      cluster: seatatest #此处注意,这的值要和nacos中的配置文件service.vgroupMapping.my_test_tx_group的值一样
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

2.Nacos添加配置文件

上一步中将nacos作为了配置中心和注册中心,所以nacos中肯定会有相应的配置文件,参照application.yml配置文件中的相关信息可以知道需要在public命名空间中的默认分组新建一个名为seataServer.proterties配置文件,将下列配置写其中,这也是Seata自带的,不过高级的版本中没有了,这里是从官网拷贝过来的。该文件主要就是配置数据库连接信息,在配置数据库连接时需要注意的是根据MySQL的版本不同,连接驱动也不一样,MySQL8以下的使用store.db.driverClassName=com.mysql.jdbc.Driver,mysql8则使用store.db.driverClassName=com.mysql.cj.jdbc.Driver

#此处需要注意,这里的配置需要在nacos中新建一个配置文件,就叫service.vgroupMapping.my_test_tx_group文件里面值就是seatatest和前面的配置registry.nacos.cluster的值是一样的
service.vgroupMapping.my_test_tx_group=seatatest
#这里的地址需要配置成seata所在服务器的地址
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#此处对于数据存储使用的是数据库存储所以需要配置数据库的连接信息
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
#数据库驱动如果是mysql8使用这个,否则使用com.mysql.jdbc.Driver 
store.db.driverClassName=com.mysql.cj.jdbc.Driver 
store.db.url=jdbc:mysql://192.168.72.130:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
#此处有四张表的配置,所以需要在数据库中执行对应的SQL创建表
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k

#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

3.数据库执行文件

前面的配置中已经知道需要在数据库中创建表了,但是seata高版本中已经不自带SQL文件了,以下是在旧版本中拷贝过来的模板,在数据库中执行一下SQL文件,其中seata数据库的配置只需要执行一次即可,undo_log文件则需要在每个业务数据库下执行一次

seata库配置

drop database if exists seata;
create database seata character set utf8 collate utf8_bin;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS seata.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_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS seata.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 = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS seata.lock_table
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS seata.distributed_lock
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

undo_log配置文件

该SQl的配置文件需要在所有的业务数据库下执行

CREATE TABLE IF NOT EXISTS seata_account.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 = utf8mb4 COMMENT ='AT transaction mode undo table';

4.启动seata

在启动之前需要先启动nacos否则会找不到服务进行注册,启动之后使用命令查看启动日志,日志一般存放在logs目录下的start.out文件中

启动日志中没有报错就是启动成功了,接着访问http://localhost:7091就可以进入登录页了,默认账户名和密码都是seata

5.注意实现

jdk版本高导致启动失败

最好是换成jdk1.8的环境来启动seata,不然会出现无法创建java虚拟的错误,这种主要有两种方式解决,一种就是换jdk的版本为1.8,还有一种就是修改seata的启动文件,但是我找了没找到相应的修改说明所以不会修改

数据库连接不到

检查连接信息中配置的数据库用户是否能够进行远程连接

#进入数据库后执行命令查看对应的用户是否支持远程连接
use mysql;

select host from user where user='root';

#如果显示的是localhost则表示不支持远程连接,需要进行修修改
update user set host='%' where user='root';

flush privileges;

检查看看Mysql的版本是8以上及以上还是8以下的,如果是8及以上则需要修改在nacos上的配置文件中的数据库连接驱动为store.db.driverClassName=com.mysql.cj.jdbc.Driver,就是第二步中的配置文件

6. 总结

客户端的配置文件针对于seata.configseata.registry配置和服务器上seata的配置文件保持一致即可,其中需要说明的就是nacos服务上新增的两个配置文件seataServer.propertiesservice.vgroupMapping.my_test_tx_group,seataServer.properties是客户端和服务器端共用的配置文件,其中配置的service.vgroupMapping.my_test_tx_group=seatatest属性是事务分组,当配置了这个并且客户端的配置文件中出现了tx-service-group: my_test_tx_group属性,那么就需要在nacos中增加相应的tx-service-group: fsp_my_group配置文件,内容为seatatest,并且确保和seataServer.properties文件在同一命名空间和同一分组下,关系图如下图所示

Seata基本使用_第1张图片


基本使用

本案例中主要用于测试使用Seata进行全局事务的管控和步使用Seata时如果发生错误会出现怎样的情况,本案例由三个部分组成,分别是SeataOrder8001SeataStorage8002SeataAccount8003,只写一个SeataOrder8001案例剩下的基本类似

业务数据库准备

执行下面的SQL语句创建对应的业务数据库

/*账户业务数据库*/
drop database if exists seata_account;
create database seata_account character set utf8 collate utf8_bin;
/*创建账户表*/
drop table if exists seata_account.account;
create table seata_account.account
(
    id              int primary key auto_increment,
    username        varchar(20) comment '用户名',
    used_money      double comment '消费金额',
    remaining_money double comment '剩余金额'
);

CREATE TABLE IF NOT EXISTS seata_account.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 = utf8mb4 COMMENT ='AT transaction mode undo table';
/*插入数据*/
insert into seata_account.account(id, username, used_money, remaining_money)
values (1, '张三', 0, 100);

/*订单业务数据库*/
drop database if exists seata_order;
create database seata_order character set utf8 collate utf8_bin;
/*创建订单表*/
drop table if exists seata_order.user_order;
create table seata_order.order
(
    id              int primary key auto_increment,
    username        varchar(20) comment '订单消费用户名',
    commodity_name  varchar(20) comment '购买的商品名称',
    commodity_count int comment '购买的商品数量',
    price           double comment '购买的商品价格',
    state           int default 0 comment '订单状态'
);

CREATE TABLE IF NOT EXISTS seata_order.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 = utf8mb4 COMMENT ='AT transaction mode undo table';

/*库存业务数据库*/
drop database if exists seata_storage;
create database seata_storage character set utf8 collate utf8_bin;
/*创建库存表*/
drop table if exists seata_storage.storage;
create table seata_storage.storage
(
    id             int primary key auto_increment,
    commodity_name varchar(20) comment '商品名称',
    leftover       int comment '剩余数量'
);

CREATE TABLE IF NOT EXISTS seata_storage.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 = utf8mb4 COMMENT ='AT transaction mode undo table';

insert into seata_storage.storage(id, commodity_name, leftover)
values (1, '牙刷', 10);

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>
    
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            
            <exclusions>
                <exclusion>
                    <groupId>io.seatagroupId>
                    <artifactId>seata-spring-boot-starterartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>io.seatagroupId>
            <artifactId>seata-spring-boot-starterartifactId>
            <version>1.5.1version>
        dependency>

        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.2.2version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.2.8version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>org.examplegroupId>
            <artifactId>command-apiartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
    dependencies>

修改配置文件

server:
  port: 8001
spring:
  application:
    name: seata-order
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.72.130:8848
        username: nacos
        password: nacos
        namespace: 2ee2fad4-97d6-4810-9b7e-97878fa85241 #1
        group: SEATA_GROUP
      config:
        server-addr: http://192.168.72.130
        username: nacos
        password: nacos
        namespace: 2ee2fad4-97d6-4810-9b7e-97878fa85241
        group: SEATA_GROUP
  datasource:
    druid:
      url: jdbc:mysql://192.168.72.130:3306/seata_order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

seata:
  enabled: true
  config:
    type: nacos
    nacos:
      data-id: seataServer.properties
      group: SEATA_GROUP
      namespace: 2ee2fad4-97d6-4810-9b7e-97878fa85241
      username: nacos
      password: nacos
      server-addr: http://192.168.72.130:8848
  registry:
    type: nacos
    nacos:
      group: SEATA_GROUP
      namespace: 2ee2fad4-97d6-4810-9b7e-97878fa85241
      username: nacos
      password: nacos
      server-addr: http://192.168.72.130:8848
      application: seata-server
      cluster: seatatest
  tx-service-group: fsp_my_group #2

1:使用命名空间的DataId不要直接使用命名空间的名称,不然可能找不到

2:2号标记处的说明看总结

编写主启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoDataSourceProxy //开启数据源代理由seata进行代理
public class SeataStorage8003 {
    public static void main(String[] args){
        SpringApplication.run(SeataStorage8003.class,args);
    }
}

编写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Accessors(chain = true)
public class Order implements Serializable {
    /**
     * 
     */
    private Integer id;

    /**
     * 订单消费用户名
     */
    private String username;

    /**
     * 购买的商品名称
     */
    private String commodityName;

    /**
     * 购买的商品数量
     */
    private Integer commodityCount;

    /**
     * 购买的商品价格
     */
    private Double price;

    /**
     * 订单状态
     */
    private Integer state;

    private static final long serialVersionUID = 1L;
}

编写映射接口

@Mapper
public interface OrderMapper{

    @Insert("insert into seata_order.order(id, username, commodity_name, commodity_count, price, state) value(#{id},#{username},#{commodityName},#{commodityCount},#{price},#{state})")
    @Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
    int addOrder(Order order);

    @Update("update seata_order.order set state=1 where id=#{orderId}")
    int updateStateById(@Param("orderId")int id);
}

编写实体类业务接口及其实现类

public interface OrderService{

    int addOrder(Order order);

    int updateStateById(int id);
}


@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Override
    public int addOrder(Order order) {
        int count=orderMapper.addOrder(order);
        return count>0?order.getId():0;
    }

    @Override
    public int updateStateById(int id) {
        return orderMapper.updateStateById(id);
    }
}

编写远程调用接口

使用feign调用Account服务和Storage服务,所以需要编写相应的Feign

@Component
@FeignClient(value = "seata-account")
public interface AccountFeignService {
    @PostMapping("/seataAccount/consumption")
    Result<String> consumption(@RequestParam("username")String username,@RequestParam("price")double price);
}


@Component
@FeignClient(value = "seata-storage")
public interface StorageFeignService {
    @PostMapping("/seataStorage/consumption")
    Result<String> consumption(@RequestParam("commodityName")String commodityName,@RequestParam("count")int count);
}

编写具体消费业务类

@Service
@Slf4j
public class ConsumptionService {
    @Resource
    private OrderServiceImpl orderService;

    @Resource
    private AccountFeignService accountFeignService;

    @Resource
    private StorageFeignService storageFeignService;

    //因为在订单服务中需要插入订单同时需要修改订单的状态所以使用 @Transactional控制全局事务
    @Transactional
    //使用@GlobalTransactional对全局事务进行处理,当调用其他微服务出现问题或自身出现问题时进行全局事务回滚
    @GlobalTransactional
    public Result<String> consumption(Order order){
        log.info("购物订单信息:{}",order.toString());
        int orderId=orderService.addOrder(order);
        //调用远程账户扣减服务
        Result<String> accountResult=accountFeignService.consumption(order.getUsername(),order.getPrice());
        //调用远程库存扣减服务
        Result<String> storageResult=storageFeignService.consumption(order.getCommodityName(),order.getCommodityCount());
        if(accountResult.getCode()==200&&storageResult.getCode()==200){
            orderService.updateStateById(orderId);
            log.info("金额扣减完成,库存扣减完成,更新订单状态");
            return new Result<>();
        }
        return new Result<>("下单失败");
    }
}

编写测试接口

@RestController
@RequestMapping("/seataOrder")
public class TestController {
    @Resource
    private ConsumptionService consumptionServer;

    @PostMapping("/consumption")
    public Result<String> consumption(@RequestBody Order order){
        return consumptionServer.consumption(order);
    }
}

整体项目目录结构

Seata基本使用_第2张图片

你可能感兴趣的:(SpringCloud,数据库,sql,服务器)