Spring Cloud Alibaba Seata 实现 TCC 事物

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案

Seata 官网:https://seata.io/zh-cn/

Spring Cloud Alibaba 官网:https://sca.aliyun.com/zh-cn/
 

版本说明

SpringBoot 版本 2.6.5

SpringCloud 版本 2021.0.1

SpringCloudAlibaba 版本 2021.0.1.0

本文详细说明

数据库服务器版本 mysql 8.0.25

mybatis plus 版本 3.5.1

nacos 版本 1.4.2

seata 客户端版本 1.4.2

seata 服务端版本 1.7.1

本文讲解的是 seata 的 TCC 事物模型,在开始阅读下面内容之前,建议先阅读笔者的这篇文章《Spring Cloud Alibaba Seata 实现分布式事物》,这篇文章中实现的是 seata 的 AT 事物,且笔者的本篇文章《Spring Cloud Alibaba Seata 实现 TCC 事物》是在《Spring Cloud Alibaba Seata 实现分布式事物》基础上写的,很多内容需要先了解,涉及seata 和nacos的重复内容,笔者在本篇文章中不在赘述,因此建议读者先看《Spring Cloud Alibaba Seata 实现分布式事物》,之后再学习本篇文章。当然,如果你对 seata 的搭建已经非常熟悉,那么可以直接开始下面阅读

目录

1、创建项目

        1.1、新建 maven 聚合项目 cloud-learn

        1.2、创建 account 服务

        1.3、创建 order 服务

2、添加配置

        2.1、客户端配置

        2.2、服务端配置

3、数据库建表

        3.1、seata 服务端建表

        3.2、seata 客户端建表

4、运行测试

5、项目代码


1、创建项目

1.1、新建 maven 聚合项目 cloud-learn

最外层父工程 cloud-learn 的 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.wsjzzcbq</groupId>
    <artifactId>cloud-learn</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>gateway-learn</module>
        <module>consumer-learn</module>
        <module>sentinel-learn</module>
        <module>seata-at-account-learn</module>
        <module>seata-at-order-learn</module>
        <module>seata-tcc-order-learn</module>
        <module>seata-tcc-account-learn</module>
    </modules>
    <packaging>pom</packaging>

    <repositories>
        <repository>
            <id>naxus-aliyun</id>
            <name>naxus-aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
        <relativePath/>
    </parent>

    <properties>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
        <alibaba-nacos-discovery.veriosn>2021.1</alibaba-nacos-discovery.veriosn>
        <alibaba-nacos-config.version>2021.1</alibaba-nacos-config.version>
        <spring-cloud-starter-bootstrap.version>3.1.1</spring-cloud-starter-bootstrap.version>
        <druid.version>1.1.17</druid.version>
        <mysql.version>8.0.11</mysql.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.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>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>${alibaba-nacos-discovery.veriosn}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                <version>${alibaba-nacos-config.version}</version>
            </dependency>

            <!--spring-cloud-dependencies 2020.0.0 版本不在默认加载bootstrap文件,如果需要加载bootstrap文件需要手动添加依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
                <version>${spring-cloud-starter-bootstrap.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>2.0.40</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>


</project>

下面会创建2个服务 account 和 order,模拟用户下订单后扣减账户金额,服务间使用 feign 调用,因为 account 和 order 服务使用不同的数据库,因此产生分布式事物,使用 seata 解决

1.2、创建 account 服务

创建子工程 seata-tcc-account-learn

seata-tcc-account-learn pom 文件

<?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>cloud-learn</artifactId>
        <groupId>com.wsjzzcbq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-tcc-account-learn</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动类 SeataTCCAccountApplication

package com.wsjzzcbq;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * SeataTCCAccountApplication
 *
 * @author wsjz
 * @date 2023/10/20
 */
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@SpringBootApplication
public class SeataTCCAccountApplication {

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

实体类 Account

package com.wsjzzcbq.bean;

import lombok.Data;

/**
 * Account
 *
 * @author wsjz
 * @date 2022/07/07
 */
@Data
public class Account {

    private Integer id;

    private String userId;

    private Integer money;
}

AccountMapper

package com.wsjzzcbq.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Account;

/**
 * AccountMapper
 *
 * @author wsjz
 * @date 2023/10/13
 */
public interface AccountMapper extends BaseMapper<Account> {
}

AccountReduceAction TCC 方法声明

package com.wsjzzcbq.action;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * AccountReduceAction
 *
 * @author wsjz
 * @date 2023/10/16
 */
@LocalTCC
public interface AccountReduceAction {

    @TwoPhaseBusinessAction(name = "account-reduce", commitMethod = "commit", rollbackMethod = "cancel")
    boolean reduce(@BusinessActionContextParameter(paramName = "userId") String userId, @BusinessActionContextParameter(paramName = "money") int money);

    boolean commit(BusinessActionContext businessActionContext);

    boolean cancel(BusinessActionContext businessActionContext);
}

这里是TCC事物,需要定义接口,加上@LocalTCC 注解标识,@TwoPhaseBusinessAction 注解声明commit 和 rollback 对应的方法,TCC 中 commit 和rollback 可阶段需要的参数可通过 @BusinessActionContextParameter 注解传递,接收时通过 BusinessActionContext 获取

AccountReduceActionImpl TCC的实际逻辑

package com.wsjzzcbq.action.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.wsjzzcbq.action.AccountReduceAction;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.service.AccountService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * AccountReduceActionImpl
 *
 * @author wsjz
 * @date 2023/10/16
 */
@Slf4j
@Component
public class AccountReduceActionImpl implements AccountReduceAction {

    @Autowired
    private AccountService accountService;

    @Override
    public boolean reduce(String userId, int money) {
        UpdateWrapper<Account> up = new UpdateWrapper<>();
        String sql = "money = money - " + money;
        up.setSql(sql);
        up.eq("user_id", userId);
        accountService.update(up);
        return true;
    }

    @Override
    public boolean commit(BusinessActionContext businessActionContext) {
        String userId = String.valueOf(businessActionContext.getActionContext("userId"));
        System.out.println(userId);
        log.info("提交成功");
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext businessActionContext) {
        String userId = String.valueOf(businessActionContext.getActionContext("userId"));
        int money = (int) businessActionContext.getActionContext("money");
        LambdaQueryWrapper<Account> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Account::getUserId, userId);
        Account account = accountService.getOne(lqw);

        UpdateWrapper<Account> uw = new UpdateWrapper<>();
        uw.set("money", account.getMoney() + money);
        uw.eq("user_id", userId);
        accountService.update(uw);
        log.info("回滚成功");
        return true;
    }
}

AccountService

package com.wsjzzcbq.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Account;

/**
 * AccountService
 *
 * @author wsjz
 * @date 2023/10/13
 */
public interface AccountService extends IService<Account> {

    String reduce(String userId, int money);

}

AccountServiceImpl

package com.wsjzzcbq.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.action.AccountReduceAction;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.mapper.AccountMapper;
import com.wsjzzcbq.service.AccountService;
import io.seata.core.context.RootContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * AccountServiceImpl
 *
 * @author wsjz
 * @date 2023/10/13
 */
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    @Autowired
    private AccountReduceAction accountReduceAction;

    @Override
    public String reduce(String userId, int money) {
        String xid = RootContext.getXID();
        System.out.println(xid);

        accountReduceAction.reduce(userId, money);
        return "ok";
    }
}

AccountController

package com.wsjzzcbq.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.service.AccountService;
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;

/**
 * AccountController
 *
 * @author wsjz
 * @date 2023/10/13
 */
@RequestMapping("/account")
@RestController
public class AccountController {

    @Autowired
    private AccountService accountService;

    @GetMapping("/find")
    public String find() throws JsonProcessingException {
        Account account = accountService.list().get(0);
        ObjectMapper objectMapper = new ObjectMapper();
        String res = objectMapper.writeValueAsString(account);
        System.out.println(res);
        return res;
    }

    @RequestMapping("/reduce")
    public String debit(String userId, int money) {
        try {
            accountService.reduce(userId, money);
            return "扣款成功";
        } catch (Exception e) {
            return "扣款失败";
        }
    }
}

application.yml 文件

server:
  port: 9001
spring:
  main:
    allow-circular-references: true
  application:
    name: seata-tcc-account-learn
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.3.232:3306/pmc-account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.2.140
      discovery:
        namespace: public
#        server-addr: 192.168.2.140
#      config:
#        server-addr:





seata:
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
      data-id: seata-tcc.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      cluster: default
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
#  tx-service-group:


logging:
  level:
    com.wsjzzcbq.mapper: debug

配置参数说明可以看《Spring Cloud Alibaba Seata 实现分布式事物》,这里不再赘述

1.3、创建 order 服务

创建子工程 seata-tcc-order-learn 项目

seata-tcc-order-learn pom 文件

<?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>cloud-learn</artifactId>
        <groupId>com.wsjzzcbq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-tcc-order-learn</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

启动类 SeataTCCOrderApplication

package com.wsjzzcbq;

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

/**
 * SeataTCCOrderApplication
 *
 * @author wsjz
 * @date 2023/10/20
 */
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@EnableFeignClients
@SpringBootApplication
public class SeataTCCOrderApplication {

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

订单实体类 Order

package com.wsjzzcbq.bean;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * Order
 *
 * @author wsjz
 * @date 2022/07/07
 */
@TableName("order_tbl")
@Data
public class Order {

    @TableId
    private Integer id;

    private String userId;

    private String code;

    private Integer count;

    private Integer money;
}

OrderMapper

package com.wsjzzcbq.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Order;

/**
 * OrderMapper
 *
 * @author wsjz
 * @date 2022/07/07
 */
public interface OrderMapper extends BaseMapper<Order> {
}

AccountFeign

package com.wsjzzcbq.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * AccountFeign
 *
 * @author wsjz
 * @date 2023/10/13
 */
@FeignClient(value = "seata-tcc-account-learn")
public interface AccountFeign {

    @RequestMapping("/account/reduce")
    String debit(@RequestParam("userId") String userId, @RequestParam("money") int money);
}

OrderServiceCreateAction TCC 方法定义

package com.wsjzzcbq.action;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * OrderServiceCreateAction
 *
 * @author wsjz
 * @date 2023/10/16
 */
@LocalTCC
public interface OrderServiceCreateAction {

    @TwoPhaseBusinessAction(name = "order-create", commitMethod = "commit", rollbackMethod = "cancel")
    void create(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money") int money,
                @BusinessActionContextParameter(paramName = "orderCode") String orderCode,
                @BusinessActionContextParameter(paramName = "rollback") boolean rollback);

    boolean commit(BusinessActionContext businessActionContext);

    boolean cancel(BusinessActionContext businessActionContext);
}

OrderServiceCreateActionImpl TCC实际方法

package com.wsjzzcbq.action.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wsjzzcbq.action.OrderServiceCreateAction;
import com.wsjzzcbq.bean.Order;
import com.wsjzzcbq.service.OrderService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * OrderServiceCreateActionImpl
 *
 * @author wsjz
 * @date 2023/10/16
 */
@Slf4j
@Component
public class OrderServiceCreateActionImpl implements OrderServiceCreateAction {

    @Autowired
    private OrderService orderService;

    @Override
    public void create(String userId, int money, String orderCode, boolean rollback) {

        Order order = new Order();
        order.setCode(orderCode);
        order.setCount(1);
        order.setUserId(userId);
        order.setMoney(money);

        orderService.save(order);
    }

    @Override
    public boolean commit(BusinessActionContext businessActionContext) {
        System.out.println("money");
        System.out.println(businessActionContext.getActionContext("money"));
        System.out.println(businessActionContext.getActionContext("orderCode"));
        log.info("提交成功");
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext businessActionContext) {
        String orderCode = String.valueOf(businessActionContext.getActionContext("orderCode"));
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("code", orderCode);
        orderService.remove(queryWrapper);
        log.info("回滚成功");
        return true;
    }
}

OrderService

package com.wsjzzcbq.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Order;

/**
 * OrderService
 *
 * @author wsjz
 * @date 2022/07/07
 */
public interface OrderService extends IService<Order> {

    void create(String userId, int money, boolean rollback);
}

OrderServiceImpl

package com.wsjzzcbq.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.action.OrderServiceCreateAction;
import com.wsjzzcbq.bean.Order;
import com.wsjzzcbq.feign.AccountFeign;
import com.wsjzzcbq.mapper.OrderMapper;
import com.wsjzzcbq.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;

/**
 * OrderServiceImpl
 *
 * @author wsjz
 * @date 2022/07/07
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Autowired
    private AccountFeign accountFeign;

    @Autowired
    private OrderServiceCreateAction orderServiceCreateAction;

    @GlobalTransactional
    @Override
    public void create(String userId, int money, boolean rollback) {
        String xid = RootContext.getXID();
        System.out.println(xid);

        String orderCode = UUID.randomUUID().toString();
        orderServiceCreateAction.create(userId, money, orderCode, rollback);

        accountFeign.debit(userId, money);

        if (rollback) {
            int a = 1/0;
        }

    }
}

OrderController

package com.wsjzzcbq.controller;

import com.wsjzzcbq.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * OrderController
 *
 * @author wsjz
 * @date 2022/07/09
 */
@RequestMapping("/order")
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * http://localhost:9002/order/create?userId=101&money=10&rollback=false
     * @param userId
     * @param money
     * @param rollback
     * @return
     */
    @RequestMapping("/create")
    public String create(String userId, int money, boolean rollback) {
        try {
            orderService.create(userId, money, rollback);
            return "下单成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "下单失败";
        }
    }

}

application.yml 文件

server:
  port: 9002
spring:
  main:
    allow-circular-references: true
  application:
    name: seata-tcc-order-learn
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.3.232:3306/pmc-order?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.2.140
      discovery:
        namespace: public
#        server-addr: 192.168.2.140
#      config:
#        server-addr:





seata:
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
      data-id: seata-tcc.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      cluster: default
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
  tx-service-group: seata-tcc-account-learn-seata-service-group

logging:
  level:
    com.wsjzzcbq.mapper: debug

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

2、添加配置

2.1、客户端配置

在nacos上新建 group 是 SEATA_GROUP,data-id 是 seata-tcc.properties 的配置,内容如下

seata-tcc.properties

#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.seata-tcc-account-learn-seata-service-group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
# You can choose from the following options: fastjson, jackson, gson
tcc.contextJsonParserType=fastjson

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

seata-tcc.properties 在《Spring Cloud Alibaba Seata 实现分布式事物》的 seata.properties 基础上修改事物分组即可

2.2、服务端配置

服务端配置和《Spring Cloud Alibaba Seata 实现分布式事物》保持一致,无需修改

3、数据库建表

3.1、seata 服务端建表

看《Spring Cloud Alibaba Seata 实现分布式事物》seata 服务端建表,保持一致,无需修改

3.2、seata 客户端建表

看《Spring Cloud Alibaba Seata 实现分布式事物》seata 客户端建表

undo_log 表不需要,保留 account 和 order_tbl 表即可

4、运行测试

启动 seata-server-1.7.1

进入 bin 目录,双击 seata-server.bat

启动 account 和 order 服务

nacos 服务和配置

测试正常情况

浏览器请求:http://localhost:9002/order/create?userId=101&money=10&rollback=false

扣减账户 10 元,新增订单

测试回滚情况

5、项目代码

码云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn

至此完

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟世君子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值