Apache Seata 基于 HighGoDB 实现分布式事务

1.Seata 简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能与简单易用的分布式事务服务,为用户提供了 AT、TCC、SAGA 和 XA 几种不同的事务模式:

  • AT模式:无侵入式的分布式事务解决方案,适合不希望对业务进行改造的场景,但由于需要添加全局事务锁,对影响高并发系统的性能。该模式主要关注多DB访问的数据一致性,也包括多服务下的多DB数据访问一致性问题
  • TCC模式:高性能的分布式事务解决方案,适用于对性能要求比较高的场景。该模式主要关注业务拆分,在按照业务横向扩展资源时,解决服务间调用的一致性问题
  • Saga模式:长事务的分布式事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统。Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统,事务参与者可以是其它公司的服务也可以是遗留系统的服务,并且对于无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式

2.Seata 的核心组件

在 Seata 中主要有以下三种角色,其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署:

  • 事务协调器(TC):维护全局事务的运行状态,负责协调并驱动全局提交或回滚
  • 事务管理器(TM):事务发起方,控制全局事务的范围,负责开启一个全局事务,并最终发起全局提交或回滚全局的决议
  • 资源管理器(RM):事务参与方,管理本地事务正在处理的资源,负责向 TC 注册本地事务、汇报本地事务状态,接收 TC 的命令来驱动本地事务的提交或回滚

3.Seata 的 AT 模式原理

AT模式,是seata的默认/独有模式,也是实际项目中比较常用的一种模式,他采用的也是两阶段提交,不过弥补了XA模式中资源锁定周期过长的缺点,相对于XA来说,性能更好一些,但缺点就是数据不是强一致,因为它的数据会真实的提交到数据库的,而如果后面做分支事务有问题的话,回滚靠的是日志来实现最终一致。

基本原理流程图:
在这里插入图片描述

阶段一RM的工作:

  1. 先会注册一个分支事务到事务协调者TC中
  2. 记录一个SQL更新前的快照和一个更新后的快照到undo_log日志表中
  3. 执行SQL并提交数据库事务
  4. 报告事务状态

阶段二RM的工作:

  1. 如果此时所有微服务都执行完,并且没有出现异常情况,事务协调者TC通知RM删除undo-log记录。
  2. 如果此时中途有微服务出现异常情况,则TC会通知RM根据undo-log记录的对应快照恢复数据到更新前。

4.SpringCloud Alibaba 整合 Seata AT 模式

4.1.开发组件介绍

名称版本
HighGoDB安全版V4.5
HgdbJdbc42.5.0
JDK1.8
Java IDEIntelliJ IDEA
spring-cloudHoxton.SR10
spring-cloud-alibaba2.2.5.RELEASE
SpringBoot2.2.4.RELEASE
mybatis7.0.0.Beta2
seata1.4.2
nacos2.2.0

4.2.安装nacos

参考Nacos2.2.0适配瀚高数据库

windows环境下,在nacos/bin目录下,进入dos输入命令startup.cmd -m standalone启动nacos
在这里插入图片描述

http://192.168.10.127:8848/nacos/index.html

用户名:nacos

密码:nacos
在这里插入图片描述

4.3.安装seata

4.3.1.下载seata-server

https://github.com/seata/seata/releases

下载安装包和源码包
在这里插入图片描述

4.3.2.增加瀚高驱动支持

安装包解压后,seata-server-1.4.2\lib路径下添加hgdb-pgjdbc-42.5.0.jar

驱动包下载地址

https://mvnrepository.com/artifact/com.highgo/hgdb-pgjdbc

4.3.3.创建相关数据库和表

参考postgresql.sql文件,可在4.1.章节下载的源码包incubator-seata-1.4.2\script\server\db路径下找到。

--创建数据库seata
create database seata;
--创建相关的表
CREATE TABLE IF NOT EXISTS public.global_table
(
    xid                       VARCHAR(128) NOT NULL,
    transaction_id            BIGINT,
    status                    SMALLINT     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                TIMESTAMP(0),
    gmt_modified              TIMESTAMP(0),
    PRIMARY KEY (xid)
);

CREATE INDEX ON public.global_table (gmt_modified, status);
CREATE INDEX ON public.global_table (transaction_id);

CREATE TABLE IF NOT EXISTS public.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            SMALLINT,
    client_id         VARCHAR(64),
    application_data  VARCHAR(2000),
    gmt_create        TIMESTAMP(6),
    gmt_modified      TIMESTAMP(6),
    PRIMARY KEY (branch_id)
);

CREATE INDEX ON public.branch_table (xid);

CREATE TABLE IF NOT EXISTS public.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),
    gmt_create     TIMESTAMP(0),
    gmt_modified   TIMESTAMP(0),
    PRIMARY KEY (row_key)
);

CREATE INDEX ON public.lock_table (branch_id);
4.3.4.配置相关
  1. 自定义命名空间

为了方便区分不同的应用,在nacos新建命名空间seata,这里创建后的命名空间,这里的命名空间id会在之后的配置中使用到
在这里插入图片描述

  1. 配置seataServer.properties
    在这里插入图片描述

Data ID: seataServer.properties

Group: SEATA_GROUP

配置格式 Properties

注意:在这里配置的Data ID 和 Group 需要和后文中配置保持一致,以确保seata能拿到存放在nacos中的配置
在这里插入图片描述

配置内容,参考配置文件config.txt,可在4.1.章节下载的源码包incubator-seata-1.4.2\script\config-center下找到。

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
#default_tx_group和值均可修改,此处使用默认,后文中客户端配置seata时要与该配置保持一致
service.vgroupMapping.default_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
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=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
#model改为db
store.mode=db
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
#数据库类型改为postgresql
store.db.dbType=postgresql
#修改数据库驱动,这里使用瀚高
store.db.driverClassName=org.postgresql.Driver
#修改为已创建的seatas数据库
store.db.url=jdbc:postgresql://192.168.100.101:5866/seata
#修改数据库用户名
store.db.user=sysdba
#修改数据库密码
store.db.password=Qwer@1234
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
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
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
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
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
  1. 配置seata-server-1.4.2\conf\file.conf
## transaction log store, only used in seata-server
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 = "postgresql"
    driverClassName = "org.postgresql.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:postgresql://192.168.100.101:5866/seata"
    user = "sysdba"
    password = "Qwer@1234"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}
  1. 配置seata-server-1.4.2\conf\registry.conf

注意:

namespace 对应nacos中自定义的seata命名空间ID

group 对应nacos中配置的 Group(SEATA_GROUP)

dataId 对应nacos中配置的 Data ID(seataServer.properties)

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

  nacos {
    application = "seata-server"
    #改为自己的nacos地址
    serverAddr = "127.0.0.1:8848"  
    #改为自己的group
    group = "SEATA_GROUP" 
    #改为自己的nacos的namespace,这里填写的是刚才创建seata命名空间的id
    namespace = "31976cce-fc16-4c7f-8c39-74f78ebe50a3" 
    cluster = "default"
    #改为自己nacos的用户名
    username = "nacos" 
    #改为自己nacos的密码
    password = "nacos"
  }
}

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

  nacos {
    #改为自己的nacos地址
    serverAddr = "127.0.0.1:8848"
    #改为自己的nacos的namespace,这里填写的是刚才创建seata命名空间的id
    namespace = "31976cce-fc16-4c7f-8c39-74f78ebe50a3"
    group = "SEATA_GROUP"
    #改为自己nacos的用户名
    username = "nacos"
    #改为自己nacos的密码
    password = "nacos"
    #改为自己的配置文件dataId
    dataId = "seataServer.properties"
  }
}
4.3.5.启动seata

cmd启动seata-server.bat
在这里插入图片描述

如图所示,没报错就是启动成功了。
在这里插入图片描述

nacos服务列表中可以看到注册的seata服务。由于我们配置命名空间为seata,因此在seata命名空间里能看到此服务。
在这里插入图片描述

4.4.项目搭建

创建三个项目分别负责下单,库存,账户余额进行演示分布式事务。

4.4.1.数据库准备

在seata库下分别创建三个模式,建表。

seata_order

CREATE SCHEMA seata_order;

CREATE TABLE seata_order.t_order  (
  id bigserial NOT NULL,
  user_id bigint DEFAULT NULL,
  product_id bigint DEFAULT NULL,
  count int DEFAULT NULL,
  money NUMERIC(11, 0) DEFAULT NULL,
  status int DEFAULT NULL,
  PRIMARY KEY (id)
);

COMMENT ON COLUMN seata_order.t_order.user_id IS '用户id';
COMMENT ON COLUMN seata_order.t_order.product_id IS '产品id';
COMMENT ON COLUMN seata_order.t_order.count IS '数量';
COMMENT ON COLUMN seata_order.t_order.money IS '金额';
COMMENT ON COLUMN seata_order.t_order.status IS '订单状态:0:创建中;1:已完结';

CREATE TABLE IF NOT EXISTS seata_order.undo_log
(
    id bigserial NOT NULL,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NOT NULL,
    rollback_info bytea NOT NULL,
    log_status INT NOT NULL,
    log_created TIMESTAMP NOT NULL,
    log_modified TIMESTAMP NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (xid, branch_id)
);

seata_storage

CREATE SCHEMA seata_storage;

CREATE TABLE seata_storage.t_storage  (
    id bigserial NOT NULL,
    product_id bigint DEFAULT NULL,
    total int DEFAULT NULL,
    used int DEFAULT NULL,
    residue int DEFAULT NULL,
    PRIMARY KEY (id)
);

COMMENT ON COLUMN seata_storage.t_storage.product_id IS '产品id';
COMMENT ON COLUMN seata_storage.t_storage.total IS '库存';
COMMENT ON COLUMN seata_storage.t_storage.used IS '已用库存';
COMMENT ON COLUMN seata_storage.t_storage.residue IS '剩余库存';

INSERT INTO seata_storage.t_storage (id, product_id, total, used, residue) VALUES (1, 1, 100, 0, 100);

CREATE TABLE IF NOT EXISTS seata_storage.undo_log
(
    id bigserial NOT NULL,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NOT NULL,
    rollback_info bytea NOT NULL,
    log_status INT NOT NULL,
    log_created TIMESTAMP NOT NULL,
    log_modified TIMESTAMP NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (xid, branch_id)
);

seata_account

CREATE SCHEMA seata_account;

CREATE TABLE IF NOT EXISTS seata_account.t_account  (
    id bigserial NOT NULL,
    user_id bigint DEFAULT NULL,
    total NUMERIC(10, 0) DEFAULT NULL,
    used NUMERIC(10, 0) DEFAULT NULL,
    residue NUMERIC(10, 0) DEFAULT NULL,
    PRIMARY KEY (id)
);

COMMENT ON COLUMN seata_account.t_account.user_id IS '用户id';
COMMENT ON COLUMN seata_account.t_account.total IS '总额度';
COMMENT ON COLUMN seata_account.t_account.used IS '已用额度';
COMMENT ON COLUMN seata_account.t_account.residue IS '剩余可用额度';

INSERT INTO seata_account.t_account (id, user_id, total, used, residue) VALUES (1, 1, 1000, 0, 1000);

CREATE TABLE IF NOT EXISTS seata_account.undo_log
(
    id bigserial NOT NULL,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NOT NULL,
    rollback_info bytea NOT NULL,
    log_status INT NOT NULL,
    log_created TIMESTAMP NOT NULL,
    log_modified TIMESTAMP NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (xid, branch_id)
);

在这里插入图片描述

4.4.2.父工程springcloud-seata-demo

在这里插入图片描述

pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>
    <groupId>com.highgo.seata</groupId>
    <artifactId>springcloud-seata-demo</artifactId>
    <version>1.0.1</version>
    <packaging>pom</packaging>

    <modules>
        <module>seata-order</module>
        <module>seata-storage</module>
        <module>seata-account</module>
    </modules>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-cloud.version>Hoxton.SR10</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
        <mybatis.spring.boot.version>2.1.4</mybatis.spring.boot.version>
        <seata.version>1.4.2</seata.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>${seata.version}</version>
            </dependency>
            <dependency>
                <groupId>com.highgo</groupId>
                <artifactId>HgdbJdbc</artifactId>
                <version>6.2.4</version>
            </dependency>
            <dependency>
                <groupId>com.highgo</groupId>
                <artifactId>hgdb-pgjdbc</artifactId>
                <version>42.5.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
4.4.3.子工程seata-order

在这里插入图片描述

pom.xml

<?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>springcloud-seata-demo</artifactId>
        <groupId>com.highgo.seata</groupId>
        <version>1.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-order</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>
        <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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>HgdbJdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>hgdb-pgjdbc</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

server:
  port: 2001
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
        namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_order
    username: sysdba
    password: Qwer@1234

seata:
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
      data-id: seataServer.properties

  tx-service-group: default_tx_group # 事务组的名称,对应service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default #指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)

feign:
  hystrix:
    enabled: true

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath*:mapper/*.xml

config配置类

MyBatisConfig

package com.highgo.seata.order.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.highgo.seata.order.dao")
public class MyBatisConfig {
}

DataSourceProxyConfig

package com.highgo.seata.order.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataSourceProxyConfig {
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    /**
     * 将阿里云提供的seata专用数据库连接池对象放入IOC容器
     */
    @Bean
    public DataSourceProxy dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return new DataSourceProxy(druidDataSource);
    }

}

实体类

CommonResult

package com.highgo.seata.order.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

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

Order

package com.highgo.seata.order.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private Long id;
    private Long userId;
    private Long productId;
    private Integer count;
    private BigDecimal money;
    /**
     * 订单状态:0:创建中;1:已完结
     */
    private Integer status;
}

Dao层

OrderDao

package com.highgo.seata.order.dao;

import com.highgo.seata.order.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface OrderDao {

    /**
     * 创建订单
     */
    void create(Order order);

    /**
     * 修改订单金额
     */
    void update(@Param("userId") Long userId,@Param("status")Integer status);

}

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.highgo.seata.order.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.highgo.seata.order.domain.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.highgo.seata.order.domain.Order">
        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>

service业务类

OrderService

package com.highgo.seata.order.service;

import com.highgo.seata.order.domain.Order;

public interface OrderService {

    void create(Order order);

}

StorageService

package com.highgo.seata.order.service;

import com.highgo.seata.order.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

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

    @PostMapping("/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId,
                          @RequestParam("count") Integer count);

}

AccountService

package com.highgo.seata.order.service;

import com.highgo.seata.order.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

@FeignClient(value = "account-service")
public interface AccountService {
    /**
     * 扣减账户余额
     */
    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId,
                          @RequestParam("money") BigDecimal money);

}

OrderServiceImpl

package com.highgo.seata.order.service.impl;

import com.highgo.seata.order.dao.OrderDao;
import com.highgo.seata.order.domain.Order;
import com.highgo.seata.order.service.AccountService;
import com.highgo.seata.order.service.OrderService;
import com.highgo.seata.order.service.StorageService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;
    @Resource
    private AccountService accountService;
    @Resource
    private StorageService storageService;

    @Override
    @GlobalTransactional(name = "create-order",rollbackFor = Exception.class)
    public void create(Order order) {
        log.info("order-service事务id---------------------->"+ RootContext.getXID());

        log.info("------->下单开始");
        //本应用创建订单
        orderDao.create(order);

        //远程调用库存服务扣减库存
        log.info("------->order-service中扣减库存开始");
        storageService.decrease(order.getProductId(),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.highgo.seata.order.controller;

import com.highgo.seata.order.domain.CommonResult;
import com.highgo.seata.order.domain.Order;
import com.highgo.seata.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

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

主启动类

package com.highgo.seata.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OrderApplication {

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

}
4.4.4.子工程seata-storage

在这里插入图片描述
pom.xml

<?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>springcloud-seata-demo</artifactId>
        <groupId>com.highgo.seata</groupId>
        <version>1.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-storage</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>
        <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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>HgdbJdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>hgdb-pgjdbc</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

server:
  port: 2002
spring:
  application:
    name: storage-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
        namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_storage
    username: sysdba
    password: Qwer@1234

seata:
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
      data-id: seataServer.properties

  tx-service-group: default_tx_group # 事务组的名称,对应service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default #指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)

feign:
  hystrix:
    enabled: true

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath*:mapper/*.xml

config配置类

MyBatisConfig

package com.highgo.seata.storage.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.highgo.seata.storage.dao")
public class MyBatisConfig {
}

DataSourceProxyConfig

package com.highgo.seata.storage.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataSourceProxyConfig {
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    /**
     * 将阿里云提供的seata专用数据库连接池对象放入IOC容器
     */
    @Bean
    public DataSourceProxy dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return new DataSourceProxy(druidDataSource);
    }

}

实体类

Storage

package com.highgo.seata.storage.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage {

    private Long id;
    private Long productId;
    private Integer total;
    private Integer used;
    private Integer residue;

}

CommonResult

package com.highgo.seata.storage.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

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

Dao层

StorageDao

package com.highgo.seata.storage.dao;

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

@Mapper
public interface StorageDao {
    void decrease(@Param("productId") Long productId, @Param("count") Integer count);

}

StorageMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.highgo.seata.storage.dao.StorageDao">

    <resultMap id="BaseResultMap" type="com.highgo.seata.storage.domain.Storage">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="product_id" property="productId" jdbcType="BIGINT"></result>
        <result column="total" property="total" jdbcType="INTEGER"></result>
        <result column="used" property="used" jdbcType="INTEGER"></result>
        <result column="residue" property="residue" jdbcType="INTEGER"></result>
    </resultMap>

    <!--减库存-->
    <update id="decrease">
        update t_storage
        set used =used + #{count},residue=residue - #{count}
        where product_id=#{productId};
    </update>
</mapper>

service业务类

StorageService

package com.highgo.seata.storage.service;

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

}

StorageServiceImpl

package com.highgo.seata.storage.service.impl;

import com.highgo.seata.storage.dao.StorageDao;
import com.highgo.seata.storage.service.StorageService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
    @Resource
    private StorageDao storageDao;

    @Override
    public void decrease(Long productId, Integer count) {
        log.info("storage-service事务id---------------------->"+ RootContext.getXID());
        log.info("------->storage-service中扣减库存开始");
        storageDao.decrease(productId,count);
        log.info("------->storage-service中扣减库存结束");
    }

}

Controller层

StorageController

package com.highgo.seata.storage.controller;

import com.highgo.seata.storage.domain.CommonResult;
import com.highgo.seata.storage.service.StorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/storage")
public class StorageController {

    @Autowired
    private StorageService storageService;

    @PostMapping(value = "/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId,count);
        return new CommonResult(200, "扣减库存成功!");
    }

}

主启动类

package com.highgo.seata.storage;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class StorageApplication {

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

}
4.4.5.子工程seata-account

在这里插入图片描述

pom.xml

<?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>springcloud-seata-demo</artifactId>
        <groupId>com.highgo.seata</groupId>
        <version>1.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-account</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>
        <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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>HgdbJdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.highgo</groupId>
            <artifactId>hgdb-pgjdbc</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

server:
  port: 2003
spring:
  application:
    name: account-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
        namespace: 31976cce-fc16-4c7f-8c39-74f78ebe50a3

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.100.101:5866/seata?currentSchema=seata_account
    username: sysdba
    password: Qwer@1234

seata:
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: ${spring.cloud.nacos.discovery.namespace}
      data-id: seataServer.properties

  tx-service-group: default_tx_group # 事务组的名称,对应service.vgroupMapping.default_tx_group=xxx中配置的default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default #指定事务分组至集群映射关系(等号右侧的集群名需要与Seata-server注册到Nacos的cluster保持一致)

feign:
  hystrix:
    enabled: true

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath*:mapper/*.xml

config配置类

MybatisConfig

package com.highgo.seata.account.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.highgo.seata.account.dao")
public class MybatisConfig {
}

DataSourceProxyConfig

package com.highgo.seata.account.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataSourceProxyConfig {
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    /**
     * 将阿里云提供的seata专用数据库连接池对象放入IOC容器
     */
    @Bean
    public DataSourceProxy dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return new DataSourceProxy(druidDataSource);
    }

}

实体类

Account

package com.highgo.seata.account.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;
    private Long userId;
    private Integer total;
    private Integer used;
    private Integer residue;
}

CommonResult

package com.highgo.seata.account.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

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

Dao层

AccountDao

package com.highgo.seata.account.dao;

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

import java.math.BigDecimal;

@Mapper
public interface AccountDao {
    /**
     * 扣减账户余额
     */
    void decrease(@Param("userId") Long userId,@Param("money") BigDecimal 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.highgo.seata.account.dao.AccountDao">
    <resultMap id="BaseResultMap" type="com.highgo.seata.account.domain.Account">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="total" property="total" jdbcType="DECIMAL"/>
        <result column="used" property="used" jdbcType="DECIMAL"/>
        <result column="residue" property="residue" jdbcType="DECIMAL"/>
    </resultMap>
    <update id="decrease">
        update t_account set residue = residue- #{money},used = used + #{money}
        where user_id =#{userId}
    </update>
</mapper>

service层

AccountService

package com.highgo.seata.account.service;

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

import java.math.BigDecimal;

public interface AccountService {

    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);

}

AccountServiceImpl

package com.highgo.seata.account.service.impl;

import com.highgo.seata.account.dao.AccountDao;
import com.highgo.seata.account.service.AccountService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;

@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountDao accountDao;

    @Override
    public void decrease(Long userId, BigDecimal money) {
        log.info("account-service事务id---------------------->"+ RootContext.getXID());
        log.info("------->account-service中扣减账户余额开始");
//        try {
//            //模拟超时异常,全局事务回滚
//            TimeUnit.SECONDS.sleep(30000);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        accountDao.decrease(userId, money);
        log.info("------->account-service中扣减账户余额结束");
    }
}

Controller层

AccountController

package com.highgo.seata.account.controller;

import com.highgo.seata.account.domain.CommonResult;
import com.highgo.seata.account.service.AccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
@Slf4j
@RestController
@RequestMapping("/account")
public class AccountController {

    @Resource
    private AccountService accountService;

    @PostMapping("/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId,
                                 @RequestParam("money") BigDecimal money) {
        accountService.decrease(userId, money);
        return new CommonResult(200, "扣减账户余额成功!");
    }

}

主启动类

package com.highgo.seata.account;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class AccountApplication {

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

}

5.场景测试

5.1.服务启动

依次启动Nacos、Seata、所有微服务

在这里插入图片描述

nacos服务列表

在这里插入图片描述

5.2.测试正常流程

服务seata-order打个断点
在这里插入图片描述

浏览器访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

当进到断点时,数据库中可以看到,seata_order、seata_storage、seata_account模式下的undo_log表,各自生成了一条日志记录,全局事务xid相同 ,说明在同一个全局事务中。
在这里插入图片描述

查看订单表、库存表和余额表,数据均已提交
在这里插入图片描述

放开断点,日志记录表被删除
在这里插入图片描述

日志输出
在这里插入图片描述

5.3.测试回滚流程

服务seata-account,模拟超时异常
在这里插入图片描述

服务seata-order,打个断点
在这里插入图片描述

浏览器访问 http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

当进入断点扣减余额时,订单和库存对应的日志表,各生成了一条记录,全局事务xid是相同的
在这里插入图片描述

订单表和库存表,订单已经创建,库存扣减完成
在这里插入图片描述

放开断点,全局事务回滚
在这里插入图片描述
日志输出
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值