【09】分布式事务Seata 之 TCC事务模式

1、准备工作

  • 第一步:先创建一个空工程:seata-tcc
  • 第二步:将无事务订单项目解压到seata-tcc文件夹下。(可访问 git 仓库 https://gitee.com/benwang6/seata-samples下载无事务订单项目)
  • 第三步:在IDEA 按两下 shift 搜索add maven project,选择需要导入的模块的pom.xml文件,将项目都导入工程中。

2、TCC事务入门案例

2.1 Order启动全局事务,添加“保存订单”分支事务

2.1.1 父工程添加seata依赖
  • 打开父工程order-parent的pom.xml文件,取消seata依赖的注释。
    	 <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-alibaba-seata</artifactId>
              <version>${spring-cloud-alibaba-seata.version}</version>
              <exclusions>
                <exclusion>
                  <artifactId>seata-all</artifactId>
                  <groupId>io.seata</groupId>
                </exclusion>
              </exclusions>
          </dependency>
          <dependency>
              <groupId>io.seata</groupId>
              <artifactId>seata-all</artifactId>
              <version>${seata.version}</version>
          </dependency>
    
2.1.2 三个配置文件
1、 application.yml
  • 设置全局事务组的组名:
    spring:
      application:
        name: order
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
        username: root
        password: root
    
      # 事务组设置  
      cloud:
        alibaba:
          seata:
            tx-service-group: order_tx_group
    server:
      port: 8083
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka
    
    mybatis-plus:
          mapper-locations: classpath:mapper/*.xml
          type-aliases-package: cn.tedu.entity
          configuration:
            map-underscore-to-camel-case: true
    logging:
      level:
        cn.tedu.mapper: debug
    
2、regirstry.conf
```go
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    # application = "default"
    # weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

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

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}
```
3、 file.conf
```go
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  # order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
  # “seata-server” 与 TC 服务器的注册名一致
  # 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
  vgroupMapping.order_tx_group = "seata-server"
  #only support when registry.type=file, please don't set multiple addresses
  order_tx_group.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}
```
2.1.3 OrderMapper 添加新的数据库操作
  • 根据前面的分析,订单数据操作有以下三项:
    • 插入订单、修改订单状态、删除订单;
    • 在 OrderMapper 中已经有插入订单的方法,现在需要添加修改订单和删除订单的方法(删除方法从BaseMapper继承):
    @Mapper
    public interface OrderMapper extends BaseMapper<Order> {
        //创建订单
        void create(Order order);
        //修改订单状态
        void updateStatus(Long id,Integer status);
    
        //删除订单--使用继承的方法 deleteById()
    }
    
2.3.4 修改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="cn.tedu.mapper.OrderMapper" >
    <resultMap id="BaseResultMap" type="cn.tedu.entity.Order" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="user_id" property="userId" jdbcType="BIGINT" />
        <result column="product_id" property="productId" jdbcType="BIGINT" />
        <result column="count" property="count" jdbcType="INTEGER" />
        <result column="money" property="money" jdbcType="DECIMAL" />
        <result column="status" property="status" jdbcType="INTEGER" />
    </resultMap>
    <insert id="create">
        INSERT INTO `order` (`id`,`user_id`,`product_id`,`count`,`money`,`status`)
        VALUES(#{id}, #{userId}, #{productId}, #{count}, #{money},#{status});
    </insert>
    <update id="updateStatus">
        update `order` set status = #{status} where id = #{id}
    </update>
    <delete id="deleteById">
        delete  from `order` where id = #{id}
    </delete>
</mapper>

2.2 Seata 实现订单的 TCC 操作方法

  • Try - prepareCreateOrder()
  • Confirm - commit()
  • Cancel - rollback()
2.2.1 添加一个工具类 ResultHolder
  • 第二阶段为了处理幂等性问题这里首先添加一个工具类 ResultHolder。
  • 幂等性控制,如果重复执行提交或回滚就和执行一次结果是一样的。
  • 这个工具也可以在第二阶段 Confirm 或 Cancel 阶段对第一阶段的成功与否进行判断,在第一阶段成功时需要保存一个标识。
    package cn.tedu.order.tcc;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class ResultHolder {
        private static Map<Class<?>, Map<String, String>> map = new ConcurrentHashMap<Class<?>, Map<String, String>>();
    
        public static void setResult(Class<?> actionClass, String xid, String v) {
            Map<String, String> results = map.get(actionClass);
    
            if (results == null) {
                synchronized (map) {
                    if (results == null) {
                        results = new ConcurrentHashMap<>();
                        map.put(actionClass, results);
                    }
                }
            }
    
            results.put(xid, v);
        }
    
        public static String getResult(Class<?> actionClass, String xid) {
            Map<String, String> results = map.get(actionClass);
            if (results != null) {
                return results.get(xid);
            }
    
            return null;
        }
    
        public static void removeResult(Class<?> actionClass, String xid) {
            Map<String, String> results = map.get(actionClass);
            if (results != null) {
                results.remove(xid);
            }
        }
    }
    
2.2.2 定义TCC接口 OrderTccAction
  • Seata 实现 TCC 操作需要定义一个接口,我们在接口中添加以下方法:
    /**
     * 为了避免seata的bug,这里不使用封装的对象,而是一个一个单独的传值。
     * BusinessActionContext 业务上下文对象,用来向第二阶段传递数据。
     * @BusinessActionContextParameter 用来把参数放入上下文对象
     */
    @LocalTCC
    public interface OrderTccAction {
        @TwoPhaseBusinessAction(name = "OrderTccAction")
        boolean prepare(BusinessActionContext ctx,
                        //该注解使该值放到上下文对象中
                        @BusinessActionContextParameter(paramName = "orderId") Long orderId,Long userId,
                        Long productId,
                        Integer count,
                        BigDecimal money);
        boolean commit(BusinessActionContext ctx);
        boolean rollback(BusinessActionContext ctx);
    
2.2.3 定义实现类 OrderTccActionImpl
  • 在每个方法上添加 @Transactional //控制本地事务。
    @Component
    public class OrderTccActionImpl implements OrderTccAction{
        @Autowired
        private OrderMapper orderMapper;
    
    
        @Transactional //控制本地事务
        @Override
        public boolean prepare(BusinessActionContext ctx, Long orderId, Long userId, Long productId, Integer count, BigDecimal money) {
            orderMapper.create(new Order(orderId,userId,productId,count,money,0));
            //一阶段成功标记
            ResultHolder.setResult(OrderTccAction.class, ctx.getXid(), "p");
            return true;
        }
    
        @Transactional
        @Override
        public boolean commit(BusinessActionContext ctx) {
            //二阶段执行之前,判断如果没有标记,则不执行二阶段操作
            if(ResultHolder.getResult(OrderTccAction.class, ctx.getXid())==null){
                return true;
            }
            //从上下文对象取出orderId
            Long orderId = Long.valueOf(ctx.getActionContext("orderId").toString());
            orderMapper.updateStatus(orderId, 1);
            //第二阶段完成时,删除标记。
            ResultHolder.removeResult(OrderTccAction.class, ctx.getXid());
            return true;
    
        }
    
        @Transactional
        @Override
        public boolean rollback(BusinessActionContext ctx) {
            //二阶段执行之前,判断如果没有标记,则不执行二阶段操作
            if(ResultHolder.getResult(OrderTccAction.class, ctx.getXid())==null){
                return true;
            }
            //从上下文对象取出orderId
            Long orderId = Long.valueOf(ctx.getActionContext("orderId").toString());
            orderMapper.deleteById(orderId);
            //第二阶段完成时,删除标记。
            ResultHolder.removeResult(OrderTccAction.class, ctx.getXid());
            return true;
        }
    }
    
2.2.4 在业务OrderServiceImpl代码中调用 Try 阶段方法
  • 业务代码中不再直接保存订单数据,而是调用 TCC 第一阶段方法prepare(),并添加全局事务注解 @GlobalTransactional;
    @Service
    public class OrderServiceImpl implements OrderService{
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private EasyIdClient easyIdClient;
    
        @Autowired
        private StorageClient storageClient;
    
        @Autowired
        private AccountClient accountClient;
    
        @Autowired
        private OrderTccAction orderTccAction;
    
        @GlobalTransactional //启动全局事务
        @Override
        public void create(Order order) {
            //远程调用ID发号器,获取订单id
            Long id = Long.valueOf(easyIdClient.nextId("order_business"));
            order.setId(id);
            //不直接完成业务数据操作,而是调用tcc的第一阶段方法
            //orderMapper.create(order);
            //orderTccAction实例,是动态代理对象,用AOP添加了切面代码,创建上下文对象,然后传入原始方法。
            orderTccAction.prepare(null,
                    order.getId(),
                    order.getUserId(),
                    order.getProductId(),
                    order.getCount(),
                    order.getMoney());
    
            //远程调用库存,减少库存
            storageClient.decrease(order.getProductId(), order.getCount());
    
            //远程调用账户,扣减账户
            accountClient.decrease(order.getUserId(), order.getMoney());
        }
    }
    

2.3 Storage添加“减少库存”分支事务

2.3.1 有三个文件需要配置:
  • application.yml
  • registry.conf
  • file.conf
  • 这三个文件的设置与上面 order 项目的配置完全相同,请参考上面订单配置一章进行配置。
2.3.2 StorageMapper 添加新的数据库操作
  • 根据前面的分析,库存数据操作有以下三项:

    • 冻结库存、冻结库存量修改为已售出量、解冻库存
      在 StorageMapper 中添加三个方法:
    @Mapper
    public interface StorageMapper extends BaseMapper<Storage> {
        void decrease(Long productId,Integer count);
        //查询商品库存,用来判断是否有足够的库存
        Storage findByProductId(Long productId);
        //可用---->冻结
        void updateResidueToFrozen(Long productId,Integer count);
        //冻结---->已售出
        void updateFrozenToUsed(Long productId,Integer count);
        //冻结---->可用
        void updateFrozenToResidue(Long productId,Integer count);
    }
    
2.3.3 修改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="cn.tedu.mapper.StorageMapper" >
    <resultMap id="BaseResultMap" type="cn.tedu.entity.Storage" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="product_id" property="productId" jdbcType="BIGINT" />
        <result column="total" property="total" jdbcType="INTEGER" />
        <result column="used" property="used" jdbcType="INTEGER" />
        <result column="residue" property="residue" jdbcType="INTEGER" />
    </resultMap>
    <update id="decrease">
        UPDATE storage SET used = used + #{count},residue = residue - #{count} WHERE product_id = #{productId}
    </update>
    <select id="findByProductId" resultMap="BaseResultMap">
        select * from storage where product_id = #{productId}
    </select>
    <update id="updateResidueToFrozen">
        update storage set Residue=Residue-#{count},Frozen=Frozen+#{count}
        where product_id =#{productId}
    </update>
    <update id="updateFrozenToUsed">
        update storage set Frozen=Frozen-#{count}, used=used+#{used}
        where product_id =#{productId}
    </update>
    <update id="updateFrozenToResidue">
        update storage set Frozen=Frozen-#{count},Residue=Residue+#{count}
        where product_id =#{productId}
    </update>
</mapper>

2.4 Seata 实现库存的 TCC 操作方法

2.4.1 添加一个工具类 ResultHolder
package cn.tedu.storage.tcc;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ResultHolder {
    private static Map<Class<?>, Map<String, String>> map = new ConcurrentHashMap<Class<?>, Map<String, String>>();

    public static void setResult(Class<?> actionClass, String xid, String v) {
        Map<String, String> results = map.get(actionClass);

        if (results == null) {
            synchronized (map) {
                if (results == null) {
                    results = new ConcurrentHashMap<>();
                    map.put(actionClass, results);
                }
            }
        }

        results.put(xid, v);
    }

    public static String getResult(Class<?> actionClass, String xid) {
        Map<String, String> results = map.get(actionClass);
        if (results != null) {
            return results.get(xid);
        }

        return null;
    }

    public static void removeResult(Class<?> actionClass, String xid) {
        Map<String, String> results = map.get(actionClass);
        if (results != null) {
            results.remove(xid);
        }
    }
}

2.4.2 定义TCC接口 StorageTccAction
  • 添加 TCC 接口,在接口中添加以下方法:
    Try - prepare()
    Confirm - commit()
    Cancel - rollback()

    @LocalTCC
    public interface StorageTccAction {
        @TwoPhaseBusinessAction(name = "StorageTccAction") //标识两阶段业务操作
        boolean prepare(BusinessActionContext ctx,
              @BusinessActionContextParameter(paramName = "productId") Long productId,//把数据放入上下文对象中,向第二阶段传递。
              @BusinessActionContextParameter(paramName = "count") Integer count);
        boolean commit(BusinessActionContext ctx);
        boolean rollback(BusinessActionContext ctx);
    
2.4.3 定义实现类 StorageTccActionImpl
@Component
public class StorageTccActionImpl implements StorageTccAction {
    @Autowired
    private StorageMapper storageMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long productId, Integer count) {
        Storage storage = storageMapper.findByProductId(productId);//查询库存够不够
        if (storage.getResidue()<count) {//库存小于冻结数量,无法继续执行冻结操作
            throw new RuntimeException("库存不足!");
        }
        storageMapper.updateResidueToFrozen(productId, count);
        //一阶段成功,添加成功的标记:StorageTccAction.class ---->  ctx.getXid() -----> "p"
        ResultHolder.setResult(StorageTccAction.class, ctx.getXid(), "p");
        return true;
    }

    @Transactional
    @Override
    public boolean commit(BusinessActionContext ctx) {
        //二阶段执行之前,判断如果没有标记,则不执行二阶段操作
        if(ResultHolder.getResult(StorageTccAction.class, ctx.getXid())==null){
            return true;
        }
        Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
        Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
        storageMapper.updateFrozenToUsed(productId, count);
        //第二阶段完成时,删除标记。
        ResultHolder.removeResult(StorageTccAction.class, ctx.getXid());
        return true;
    }

    @Transactional
    @Override
    public boolean rollback(BusinessActionContext ctx) {
        //二阶段执行之前,判断如果没有标记,则不执行二阶段操作
        if(ResultHolder.getResult(StorageTccAction.class, ctx.getXid())==null){
            return true;
        }
        Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
        Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
        storageMapper.updateFrozenToResidue(productId, count);
        //第二阶段完成时,删除标记。
        ResultHolder.removeResult(StorageTccAction.class, ctx.getXid());
        return true;
    }
}
2.4.4 在业务 StorageServiceImpl 代码中调用 Try 阶段方法
@Service
@GlobalTransactional //启动全局事务
public class StorageServiceImpl implements StorageService{
  
    @Autowired
    private StorageTccAction storageTccAction;


    @Override
    public void decrease(Long productId, Integer count) {
        storageTccAction.prepare(null, productId, count);
    }
}

2.5 Account添加“扣减金额”分支事务

2.5.1 有三个文件需要配置:
  • application.yml
  • registry.conf
  • file.conf
  • 这三个文件的设置与上面 order 项目的配置完全相同,请参考上面订单配置一章进行配置。
2.5.2 AccountMapper 添加新的数据库操作
  • 根据前面的分析,库存数据操作有以下三项:
    冻结账户、冻结账户量修改为消费量、解冻账户
    在 AccountMapper 中添加三个方法:
    @Mapper
    
    public interface AccountMapper extends BaseMapper<Account> {
        //扣减账户金额
        void decrease(Long userId, BigDecimal money);
        //查询账户
        Account findUserId(Long userId);
        //可用 ----> 冻结
        void updateResidueToFrozen(Long userId, BigDecimal money);
        //冻结 ----> 已消费
        void updateFrozenToUsed(Long userId, BigDecimal money);
        //冻结 ----> 可用
        void updateFrozenToResidue(Long userId, BigDecimal money);
    }
    
2.5.3 修改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="cn.tedu.mapper.AccountMapper" >
    <resultMap id="BaseResultMap" type="cn.tedu.entity.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"/>
        <result column="frozen" property="frozen" jdbcType="DECIMAL"/>
    </resultMap>
    <update id="decrease">
        UPDATE account SET residue = residue - #{money},used = used + #{money} where user_id = #{userId};
    </update>
    <select id="selectById" resultMap="BaseResultMap">
        SELECT * FROM account WHERE user_id=#{userId}
    </select>

    <update id="updateFrozen">
        UPDATE account SET residue=#{residue},frozen=#{frozen} WHERE user_id=#{userId}
    </update>

    <update id="updateFrozenToUsed">
        UPDATE account SET frozen=frozen-#{money}, used=used+#{money} WHERE user_id=#{userId}
    </update>

    <update id="updateFrozenToResidue">
        UPDATE account SET frozen=frozen-#{money}, residue=residue+#{money} WHERE user_id=#{userId}
    </update>
</mapper>

2.6 Seata 实现账户的 TCC 操作方法

2.6.1 添加一个工具类ResultHolder
package cn.tedu.account.tcc;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ResultHolder {
    private static Map<Class<?>, Map<String, String>> map = new ConcurrentHashMap<Class<?>, Map<String, String>>();

    public static void setResult(Class<?> actionClass, String xid, String v) {
        Map<String, String> results = map.get(actionClass);

        if (results == null) {
            synchronized (map) {
                if (results == null) {
                    results = new ConcurrentHashMap<>();
                    map.put(actionClass, results);
                }
            }
        }

        results.put(xid, v);
    }

    public static String getResult(Class<?> actionClass, String xid) {
        Map<String, String> results = map.get(actionClass);
        if (results != null) {
            return results.get(xid);
        }

        return null;
    }

    public static void removeResult(Class<?> actionClass, String xid) {
        Map<String, String> results = map.get(actionClass);
        if (results != null) {
            results.remove(xid);
        }
    }
}
2.6.2 定义TCC接口AccountTccAction
@LocalTCC
public interface AccountTccAction {
    @TwoPhaseBusinessAction(name = "AccountTccAction",commitMethod = "commit", rollbackMethod = "rollback")
    boolean prepare(BusinessActionContext ctx,
                    //该注解使该值放到上下文对象中
                    @BusinessActionContextParameter(paramName = "userId") Long userId,
                    @BusinessActionContextParameter(paramName = "money") BigDecimal money);
    boolean commit(BusinessActionContext ctx);
    boolean rollback(BusinessActionContext ctx);
}
2.6.3 定义实现类AccountTccActionImpl
@Component
@SLF4j
public class AccountTccActionImpl implements AccountTccAction {
    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long userId, BigDecimal money) {
    log.info("减少账户金额,第一阶段锁定金额,userId="+userId+", money="+money);
        //BigDecimal是对象不能直接比大小,要用compareTo()方法来进行比值
        Account account = accountMapper.findUserId(userId);
        if (account.getResidue().compareTo(money) < 0) {
            throw new RuntimeException("可用金额不足");
        }
        accountMapper.updateResidueToFrozen(userId, money);
        ResultHolder.setResult(AccountTccAction.class , ctx.getXid(), "p");
        return true;
    }

    @Transactional
    @Override
    public boolean commit(BusinessActionContext ctx) {
        if(ResultHolder.getResult(AccountTccAction.class, ctx.getXid())==null){
            return true;
        }
       Long userId = Long.valueOf(ctx.getActionContext("userId").toString());
       BigDecimal money = new BigDecimal(ctx.getActionContext("money").toString());
       accountMapper.updateFrozenToUsed(userId, money);
       ResultHolder.removeResult(AccountTccAction.class, ctx.getXid());
        return true;
    }

    @Transactional
    @Override
    public boolean rollback(BusinessActionContext ctx) {
        if(ResultHolder.getResult(AccountTccAction.class, ctx.getXid())==null){
            return true;
        }
        Long userId = Long.valueOf(ctx.getActionContext("userId").toString());
        BigDecimal money = new BigDecimal(ctx.getActionContext("money").toString());
        accountMapper.updateFrozenToResidue(userId,money);
        ResultHolder.removeResult(AccountTccAction.class, ctx.getXid());
        return true;
    }
}
2.6.4 在业务AccountServiceImpl代码中调用Try阶段方法
@Service
@GlobalTransactional //启动全局事务
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountTccAction accountTccAction;

    @Override
    public void decrease(Long userId, BigDecimal money) {
        accountTccAction.prepare(null, userId, money);
    }
}

2.7 测试

按顺序启动服务:
Eureka
Seata Server
Easy Id Generator
Storage
Account
Order
调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100

观察 account 的控制台日志或数据库的数据变化:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值