Seata1.5.2

1、下载Seata服务端

 下载Seata服务端Releases · seata/seata · GitHub

2、Seata配置Nacos

修改conf/application.yml

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:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata152
      group: SEATA_GROUP
      username: nacos
      password: nacos
      data-id: seataServer.properties

  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      cluster: default
      username: nacos
      password: nacos
#  store: #这个配置作用不大,因为上面在引入的nacos配置的时候,又会再引入一遍数据库的配置
    # support: file 、 db 、 redis
#    mode: db
     
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

3、Nacos配置

Nacos创建seata152命令空间

在创建seataServer.properties配置文件(application.yml配置文件中的data-id:),Group为SEATA_GROUP

配置内容pseata/config.txt at develop · seata/seata · GitHub

将配置中的数据库修改成自己的数据库连接,账号,密码。

store.mode=db
store.lock.mode=db
store.session.mode=db

store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata152?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root123
store.db.minConn=5
store.db.maxConn=30
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

service.vgroupMapping.account_tx_group=default
service.vgroupMapping.storage_tx_group=default
service.vgroupMapping.order_tx_group=default

4、创建seata数据库

mysql:https://github.com/seata/seata/tree/v1.5.2/script/server/db/mysql.sql
oracle:https://github.com/seata/seata/tree/v1.5.2/script/server/db/oracle.sql
postgresql:https://github.com/seata/seata/tree/v1.5.2/script/server/db/postgresql.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_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 `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 `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 `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 `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

配置完后启动Seata。执行bin/seata-server.bat的文件。查看nacos可以看到seata已经注册到nacos上面了。

5、创建seata-account-service服务。

创建152_db_account数据库,执行下面的SQL创建表。

CREATE TABLE `t_account` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint DEFAULT NULL COMMENT '用户id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
 
INSERT INTO `t_account` VALUES ('1', '1', '1000', '0', '1000');
 
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';

109-springcloud-alibaba-seate152的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>

    <groupId>com.zmm</groupId>
    <artifactId>109-springcloud-alibaba-seate152</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>seata-order-service-8501</module>
        <module>seata-account-service-8502</module>
        <module>seata-storage-service-8500</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud Alibaba 的版本信息-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.9.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--Spring Cloud 的版本信息-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

添加seata-account-service服务的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>109-springcloud-alibaba-seate152</artifactId>
        <groupId>com.zmm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-account-com.zmm.service-8502</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

创建bootstrap.yml文件

# Tomcat
server:
  port: 8502

# Spring
spring:
  application:
    # 应用名称
    name: seata-account-service
  profiles:
    # 环境配置
    active: dev
  main:
    allow-circular-references: true
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
      config:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
        file-extension: yml
        # 共享配置
        #shared-configs:
        #  - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://127.0.0.1:3306/152_db_account?serverTimezone=UTC
    username: root
    password: root123
    #dynamic:
    #  seata: true    # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭


seata:
  enabled: true
  application-id: ${spring.application.name}
  # 自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
  tx-service-group: account_tx_group
  service:
    vgroup-mapping:
      # 自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
      account_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos

###################################### MyBatis 配置 ######################################
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mapper/*.xml
  #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
  type-aliases-package: com.zmm.entity
  configuration:
    #默认开启驼峰命名法,可以不用设置该属性
    map-underscore-to-camel-case: true

management:
  endpoints:
    web:
      exposure:
        include: "*"   # * 在yaml 文件属于关键字,所以需要加引号

创建AccountMapper

@Mapper
public interface AccountMapper {
 
    Account selectByUserId(Long userId);
 
    int decrease(Long userId, 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.zmm.mapper.AccountMapper">
    <resultMap id="BaseResultMap" type="com.zmm.entity.Account">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <result column="total" jdbcType="DECIMAL" property="total"/>
        <result column="used" jdbcType="DECIMAL" property="used"/>
        <result column="residue" jdbcType="DECIMAL" property="residue"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , user_id, total, used, residue
    </sql>
    <select id="selectByUserId" resultType="com.zmm.entity.Account">
        select
        <include refid="Base_Column_List"/>
        from t_account
        where user_id = #{userId,jdbcType=BIGINT}
    </select>
    <update id="decrease">
        UPDATE t_account
        SET residue = residue - #{money},
            used    = used + #{money}
        WHERE user_id = #{userId};
    </update>
</mapper>

创建Account 

@Data
public class Account {
 
    private Long id;
    private Long userId;
    private BigDecimal total;
    private BigDecimal used;
    private BigDecimal residue;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
    private Integer code;
    private String message;
    private T data;
    public CommonResult(Integer code, String message) {
        this(code, message, null);
    }
 
}

创建AccountService

public interface AccountService {
 
    int decrease(Long userId, BigDecimal money) ;
}
@Service
@Slf4j
public class AccountServiceImpl implements AccountService{

    @Resource
    AccountMapper accountMapper;

    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public int decrease(Long userId, BigDecimal money) {
        log.info("Seata全局事务id=================>{}", RootContext.getXID());

        log.info("------->account-com.zmm.service 开始查询账户余额");
        Account account = accountMapper.selectByUserId(userId);
        log.info("------->account-com.zmm.service 账户余额查询完成," + account);
        if (account != null && account.getResidue().intValue() >= money.intValue()) {
            log.info("------->account-com.zmm.service 开始从账户余额中扣钱!");
            int decrease = accountMapper.decrease(userId, money);
            log.info("------->account-com.zmm.service 从账户余额中扣钱完成");
            return decrease;
        } else {
            log.info("账户余额不足,开始回滚!");
            throw new RuntimeException("账户余额不足!");
        }
    }
}

创建DataSourceConfig

@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.name}")
    private String name;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Bean
    @Primary
    public DataSource dataSource(){

        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setName(name);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);

        DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
        return dataSourceProxy;
    }
}

创建AccountController 

@RestController
@Slf4j
public class AccountController {
 
    @Resource
    AccountServiceImpl accountService;
 
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
 
        int decrease=accountService.decrease(userId,money);
 
        return new CommonResult(200, "扣款成功",decrease);
    }
}

创建Seata152Application8502

@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@RefreshScope
public class Seata152Application8502 {

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

6、创建seata-order-service服务。

创建152_db_order数据库,执行下面的SQL创建表。

CREATE TABLE `t_order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint DEFAULT NULL COMMENT '用户id',
  `product_id` bigint DEFAULT NULL COMMENT '产品id',
  `count` int DEFAULT NULL COMMENT '数量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  `status` int DEFAULT NULL COMMENT '订单状态:0:未完成;1:已完结',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb3;
 

CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';

添加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>109-springcloud-alibaba-seate152</artifactId>
        <groupId>com.zmm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-order-com.zmm.service-8501</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

创建bootstrap.yml文件

# Tomcat
server:
  port: 8501

# Spring
spring:
  application:
    # 应用名称
    name: seata-order-service
  profiles:
    # 环境配置
    active: dev
  main:
    allow-circular-references: true
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
      config:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
        file-extension: yml
        # 共享配置
        #shared-configs:
        #  - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}


  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://127.0.0.1:3306/152_db_order?serverTimezone=UTC
    username: root
    password: root123

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: order_tx_group
  service:
    vgroup-mapping:
      order_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos

###################################### MyBatis 配置 ######################################
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mapper/*.xml
  #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
  type-aliases-package: com.zmm.entity
  configuration:
    #默认开启驼峰命名法,可以不用设置该属性
    map-underscore-to-camel-case: true

management:
  endpoints:
    web:
      exposure:
        include: "*"   # * 在yaml 文件属于关键字,所以需要加引号

创建CommonResult

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
    private Integer code;
    private String message;
    private T data;
    public CommonResult(Integer code, String message) {
        this(code, message, null);
    }
 
}
@Data
public class Order {
 
    private Long id;
    private Long userId;
    private Long productId;
    private Integer count;
    private BigDecimal money;
    private Integer status;
}

创建OrderMapper

@Mapper
public interface OrderMapper {
 
 
    int create(Order order);
 
    //2 修改订单状态,从零改为1
    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.zmm.mapper.OrderMapper">
    <resultMap id="BaseResultMap" type="com.zmm.entity.Order">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="user_id" jdbcType="BIGINT" property="userId"/>
        <result column="product_id" jdbcType="BIGINT" property="productId"/>
        <result column="count" jdbcType="INTEGER" property="count"/>
        <result column="money" jdbcType="DECIMAL" property="money"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , user_id, product_id, count, money, status
    </sql>
 
    <insert id="create" parameterType="com.zmm.entity.Order">
        insert into t_order (user_id, product_id,
                             count, money, status)
        values (#{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT},
                #{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER})
 
    </insert>
 
    <update id="update">
        update t_order
        set status = 1
        where user_id = #{userId}
          and status = #{status};
    </update>
</mapper>

创建OrderService 

public interface OrderService {
 
    /**
     * 创建订单数据
     * @param order
     */
    CommonResult create(Order order);
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;
    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:下订单->扣库存->减余额->改订单状态
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public CommonResult create(Order order) {
        log.info("Seata全局事务id=================>{}", RootContext.getXID());

        log.info("----->开始新建订单");
        //1 新建订单
        order.setUserId(new Long(1));
        order.setStatus(0);
        orderMapper.create(order);
        //2 扣减库存
        log.info("----->订单服务开始调用库存服务,开始扣减库存");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("----->订单微服务开始调用库存,扣减库存结束");
        //3 扣减账户
        log.info("----->订单服务开始调用账户服务,开始从账户扣减商品金额");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("----->订单微服务开始调用账户,账户扣减商品金额结束");
        //4 修改订单状态,从零到1,1代表已经完成
        log.info("----->修改订单状态开始");
        orderMapper.update(order.getUserId(), 0);
        log.info("----->修改订单状态结束");
        log.info("----->下订单结束了------->");
        return new CommonResult(200, "订单创建成功");
    }
}

创建AccountService 远程调用

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

创建StorageService 远程调用

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

创建OrderController 

@RestController
public class OrderController {
 
    @Autowired
    private OrderServiceImpl orderService;
    @GetMapping("/order/create/{productId}/{count}/{money}")
    public CommonResult create(@PathVariable("productId") Integer productId, @PathVariable("count") Integer count
            , @PathVariable("money") BigDecimal money) {
        Order order = new Order();
        order.setProductId(Integer.valueOf(productId).longValue());
        order.setCount(count);
        order.setMoney(money);
        return orderService.create(order);
    }
}
@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.name}")
    private String name;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Bean
    @Primary
    public DataSource dataSource(){

        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setName(name);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);

        DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
        return dataSourceProxy;
    }
}

创建Seata152Application8501 启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RefreshScope
public class Seata152Application8501 {

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

7、创建seata-storage-service服务。

创建152_db_storage数据库,执行下面的SQL创建表。

CREATE TABLE `t_storage` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `product_id` bigint DEFAULT NULL COMMENT '产品id',
  `total` int DEFAULT NULL COMMENT '总库存',
  `used` int DEFAULT NULL COMMENT '已用库存',
  `residue` int DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
 
INSERT INTO `130_db_storage`.`t_storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES (1, 1, 100, 0, 100);
 
 
CREATE TABLE `undo_log` (
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';

添加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>109-springcloud-alibaba-seate152</artifactId>
        <groupId>com.zmm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-storage-com.zmm.service-8500</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

创建bootstrap.yml文件

# Tomcat
server:
  port: 8500

# Spring
spring:
  application:
    # 应用名称
    name: seata-storage-service
  profiles:
    # 环境配置
    active: dev
  main:
    allow-circular-references: true
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
      config:
        # 注册组 要与 seata 相同
        group: SEATA_GROUP
        server-addr: 127.0.0.1:8848
        namespace: seata152
        file-extension: yml
        # 共享配置
        #shared-configs:
        #  - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}


  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: defaultDataSource
    url: jdbc:mysql://127.0.0.1:3306/152_db_storage?serverTimezone=UTC
    username: root
    password: root123
    #dynamic:
    #  seata: true    # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: storage_tx_group
  service:
    vgroup-mapping:
      storage_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: seata152
      username: nacos
      password: nacos

###################################### MyBatis 配置 ######################################
mybatis:
  # 指定 mapper.xml 的位置
  mapper-locations: classpath:mapper/*.xml
  #扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
  type-aliases-package: com.zmm.entity
  configuration:
    #默认开启驼峰命名法,可以不用设置该属性
    map-underscore-to-camel-case: true

management:
  endpoints:
    web:
      exposure:
        include: "*"   # * 在yaml 文件属于关键字,所以需要加引号

创建实体

@Data
public class Storage {
 
    private Long id;
    private Long productId;
    private Integer total;
    private Integer used;
    private Integer residue;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
    private Integer code;
    private String message;
    private T data;
    public CommonResult(Integer code, String message) {
        this(code, message, null);
    }
 
}

创建StorageMapper 

@Mapper
public interface StorageMapper {
 
    Storage selectByProductId(Long productId);
 
 
    int decrease(Storage record);
}

创建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.zmm.mapper.StorageMapper">
    <resultMap id="BaseResultMap" type="com.zmm.entity.Storage">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="product_id" jdbcType="BIGINT" property="productId"/>
        <result column="total" jdbcType="INTEGER" property="total"/>
        <result column="used" jdbcType="INTEGER" property="used"/>
        <result column="residue" jdbcType="INTEGER" property="residue"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
        , product_id, total, used, residue
    </sql>
    <update id="decrease" parameterType="com.zmm.entity.Storage">
        update t_storage
        <set>
            <if test="total != null">
                total = #{total,jdbcType=INTEGER},
            </if>
            <if test="used != null">
                used = #{used,jdbcType=INTEGER},
            </if>
            <if test="residue != null">
                residue = #{residue,jdbcType=INTEGER},
            </if>
        </set>
        where product_id = #{productId,jdbcType=BIGINT}
    </update>
 
    <select id="selectByProductId" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from t_storage
        where product_id = #{productId,jdbcType=BIGINT}
    </select>
</mapper>

创建StorageService 

public interface StorageService {
 
    int decrease(Long productId, Integer count) ;
}
@Service
@Slf4j
public class StorageServiceImpl implements StorageService{

    @Resource
    StorageMapper storageMapper;

    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public int decrease(Long productId, Integer count) {
        log.info("Seata全局事务id=================>{}", RootContext.getXID());

        log.info("------->storage-service中扣减库存开始");
        log.info("------->storage-com.zmm.service 开始查询商品是否存在");
        Storage storage = storageMapper.selectByProductId(productId);
        if (storage != null && storage.getResidue().intValue() >= count.intValue()) {
            Storage storage2 = new Storage();
            storage2.setProductId(productId);
            storage.setUsed(storage.getUsed() + count);
            storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
            int decrease = storageMapper.decrease(storage);
            log.info("------->storage-com.zmm.service 扣减库存成功");
            return decrease;
        } else {
            log.info("------->storage-com.zmm.service 库存不足,开始回滚!");
            throw new RuntimeException("库存不足,扣减库存失败!");
        }
    }
}

创建StorageController 

@RestController
@Slf4j
public class StorageController {
 
    @Resource
    private StorageServiceImpl storageService;
 
    @Value("${server.port}")
    private String serverPort;
 
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) {
        int decrease = storageService.decrease(productId, count);
        CommonResult result;
        if (decrease > 0) {
            result = new CommonResult(200, "from mysql,serverPort:  " + serverPort, decrease);
        } else {
            result = new CommonResult(505, "from mysql,serverPort:  " + serverPort, "库存扣减失败");
        }
        return result;
    }
}

创建DataSourceConfig 

@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.name}")
    private String name;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Bean
    @Primary
    public DataSource dataSource(){

        DruidDataSource druidDataSource=new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setName(name);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);

        DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
        return dataSourceProxy;
    }
}

创建Seata152Application8500 启动类

@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@RefreshScope
public class Seata152Application8500 {

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

8、测试

创建完三个服务后分别启动来。

 用浏览器调用http://127.0.0.1:8501/order/create/1/2/50

 在分别看三个数据库数据。

 

 

 

可以看到订单已经完成了。数据也都记录下来。

在调用 http://127.0.0.1:8501/order/create/1/2/5000

 可以看到后端服务已经报错。在看下三个数据库数据

报错后没有参数脏数据。说明分布式事务已经生效了。

9、集成Seata1.5.2中的问题。

分布式事务:Seata框架AT模式及TCC模式执行流程剖析 - 朝雨忆轻尘 - 博客园

集成Seata1.5.2后,调用报错方法发现事务没有生效。在排查的过程中发现undo_log表没有存入数据。只需要手动注入DataSourceProxy数据源就可以了。


                

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值