分布式事务03-Seata框架-Spring Cloud微服务添加TCC 分布式事务

TCC

1. 简介

  • TCC模式即将每个服务业务操作分为两个阶段:
    • 第一个阶段: 检查并预留(冻结)相关资源,可视为一种临时操作
    • 第二阶段根: 据所有服务业务的Try状态来操作,如果都成功,则进行Confirm操作,如果任意一个Try发生错误,则全部Cancel,特征在于它不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务,不同于AT的是就是需要自行定义各个阶段的逻辑,对业务有侵入。
  • TCC使用要求就是业务接口都必须实现三段逻辑:
    • 准备操作 Try:完成所有业务检查,预留必须的业务资源。
    • 确认操作 Confirm:真正执行的业务逻辑,不做任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务能且只能成功一次。
    • 取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

2. 基本原理

TCC 与 Seata AT 事务一样都是两阶段事务,它与 AT 事务的主要区别为:

  • TCC 对业务代码侵入严重
    每个阶段的数据操作都要自己进行编码来实现,事务框架无法自动处理。
  • TCC 效率更高
    不必对数据加全局锁,允许多个事务同时操作数据。
    在这里插入图片描述

2.1 第一阶段 Try

假如用户购买 100 元商品,要扣减 100 元。
TCC 事务首先对这100元的扣减金额进行预留,或者说是先冻结这100元:在这里插入图片描述

2.2 第二阶段 Confirm/Cancel

  • Confirm
    如果第一阶段能够顺利完成,那么说明“扣减金额”业务(分支事务)最终肯定是可以成功的。当全局事务提交时, TC会控制当前分支事务进行提交,如果提交失败,TC 会反复尝试,直到提交成功为止。当全局事务提交时,就可以使用冻结的金额来最终实现业务数据操作:在这里插入图片描述
  • Cancel
    如果全局事务回滚,就把冻结的金额进行解冻,恢复到以前的状态,TC 会控制当前分支事务回滚,如果回滚失败,TC 会反复尝试,直到回滚完成为止。在这里插入图片描述

多个事务并发

多个TCC全局事务允许并发,它们执行扣减金额时,只需要冻结各自的金额即可:
.在这里插入图片描述

3. seate-TCC事务案例

3.1 项目创建

将之前无事务版本seata项目解压到新项目中即可
在这里插入图片描述

3.2 开启seata server-TCC事务协调器

开启之前的seata server
在这里插入图片描述

4. order添加TCC事务

4.1 添加依赖

将orer-parent项目中的pom.xml文件中注释的依赖解除注释
在这里插入图片描述

4.1 resource配置

复制之前项目配置文件即可

  • application.yml - 事务组组名
  • registry.conf - eureak地址
  • file.conf - 事务组对应的协调器
    在这里插入图片描述

4.2 添加TCC操作接口和实现类

4.2.1 OrderMapper 添加更新订单状态、删除订单

package cn.tedu.mapper;

import cn.tedu.entity.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface OrderMapper extends BaseMapper<Order> {
    void create(Order order);

    void updateStatus(Long produtId, Integer status);


}

4.2.2 修改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`=#{orderId};
    </update>
    <delete id="deleteById">
        DELETE FROM `order` WHERE `id`=#{orderId}
    </delete>
</mapper>

4.3 添加TCC接口和实现

4.3.1 接口

package cn.tedu.tcc;

import cn.tedu.entity.Order;
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;

import java.math.BigDecimal;

@LocalTCC
public interface OrderTccAction {

    /**
     * BusinessActionContext ctx
     * TCC的上下文对象,通过ctx对象,在两个阶段传递业务数据
     *
     * @BusinessActionContextParameter
     * 把数据放入上下文对象,通过上下文对象可以把数据传递到第二阶段
     *
     * @return
     */

    //try
    @TwoPhaseBusinessAction(name = "OrderTCCAction")
    boolean prepare(BusinessActionContext ctx,
                    @BusinessActionContextParameter(paramName = "id") Long id,
                    @BusinessActionContextParameter(paramName = "userId") Long userId,
                    @BusinessActionContextParameter(paramName = "productId") Long productId,
                    @BusinessActionContextParameter(paramName = "count") Integer count,
                    @BusinessActionContextParameter(paramName = "money") BigDecimal money,
                    @BusinessActionContextParameter(paramName = "status") Integer status);

    //Confirm
    boolean commit(BusinessActionContext ctx);

    //Cancel
    boolean rollback(BusinessActionContext ctx);
}

4.3.2 实现类

package cn.tedu.tcc;

import cn.tedu.entity.Order;
import cn.tedu.mapper.OrderMapper;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Component
public class OrderTccActionImpl implements OrderTccAction {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long id, Long userId, Long productId, Integer count, BigDecimal money, Integer status) {
        //插入冻结订单
        Order order = new Order(id, userId, productId, count, money, 0);
        orderMapper.create(order);
        return true;
    }

    @Transactional
    @Override
    public boolean commit(BusinessActionContext ctx) {
        /**
         * ctx在向第二阶段传递时,会先转成json,再转回上下文对象
         * 其中的整数在转换过程中可能变成Integer,也可能是Long
         */
        Long orderId = Long.valueOf(ctx.getActionContext("id").toString());
        //修改状态为正常
        orderMapper.updateStatus(orderId, 1);
        return true;
    }
    
    @Transactional
    @Override
    public boolean rollback(BusinessActionContext ctx) {
        //删除订单
        Long orderId = Long.valueOf(ctx.getActionContext("id").toString());
        orderMapper.deleteById(orderId);
        return true;
    }
}

4.4 修改OrderServiceImpl

package cn.tedu.service;

import cn.tedu.entity.Order;
import cn.tedu.feign.AccountClient;
import cn.tedu.feign.EasyIdClient;
import cn.tedu.feign.StorageClient;
import cn.tedu.mapper.OrderMapper;
import cn.tedu.tcc.OrderTccAction;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service

public class OrderServiceImpl implements OrderService{

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private AccountClient accountClient;

    @Autowired
    private StorageClient storageClient;

    @Autowired
    private EasyIdClient easyIdClient;

    @Autowired
    private OrderTccAction orderTccAction;

    @Override
    @GlobalTransactional//启动全局事务
    public void create(Order order) {
        // TODO: 从全局唯一id发号器获得id,这里暂时随机产生一个 orderId
//        Long orderId = Long.valueOf(new Random().nextInt(Integer.MAX_VALUE));
//        order.setId(orderId);

        //调用全局唯一id发号器,获取一个唯一的id
        String s = easyIdClient.nextId("order_business");
        Long orderId = Long.valueOf(s);

//        orderMapper.create(order);
        /*
        orderTccAction 是一个动态代理对象,其中AOP添加了拦截器,会拦截调用,在拦截器中创建上下文对象
         */

        orderTccAction.prepare(null, order.getId(), order.getUserId(), order.getProductId(), order.getCount(), order.getMoney(), order.getStatus());

        // TODO: 调用storage,修改库存
//        storageClient.decrease(order.getProductId(), order.getCount());
        // TODO: 调用account,修改账户余额
//        accountClient.decrease(order.getUserId(), order.getMoney());

    }

}

4.5 启动order测试

调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
查看控制台:
在这里插入图片描述

5. storage添加TCC事务

5.1 复制resource配置以及ResultHolder工具类

在这里插入图片描述

5.2 修改StorageMapper接口

package cn.tedu.mapper;

import cn.tedu.entity.Storage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface StorageMapper extends BaseMapper<Storage> {
    void decrease(Long productId, Integer count);

    //根据id查询库存shuju,zhijie使用继承的方法 selecetById()

    //冻结库存
    void updateFrozen(Long productId, Integer count);

    //把冻结库存变成已售出
    void updateFrozenToUsed(Long productId, Integer count);

    //把冻结库存恢复成可用库存
    void updateFrozenToResidue(Long productId, Integer count);
}

5.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="selectById" resultMap="BaseResultMap">
        select * from storage where product_id = #{productId}
    </select>
    <update id="updateFrozen">
        UPDATE storage SET
        residue=#{residue},
        frozen=#{frozen}
        WHERE product_id=#{productId}
    </update>

    <update id="updateFrozenToUsed">
        UPDATE storage SET
        frozen=frozen-#{count},
        used=used+#{count}
        WHERE product_id=#{productId}
    </update>

    <update id="updateFrozenToResidue">
        UPDATE storage SET
        frozen=frozen-#{count},
        residue=residue+#{count}
        WHERE product_id=#{productId}
    </update>
</mapper>

5.4 添加TCC接口和实现类

package cn.tedu.tcc;

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;

import java.math.BigDecimal;

@LocalTCC
public interface StorageTccAction {

    /**
     * BusinessActionContext ctx
     * TCC的上下文对象,通过ctx对象,在两个阶段传递业务数据
     *
     * @BusinessActionContextParameter
     * 把数据放入上下文对象,通过上下文对象可以把数据传递到第二阶段
     *
     * @return
     */

    //try
    @TwoPhaseBusinessAction(name = "StorageTCCAction")
    boolean prepare(BusinessActionContext ctx,
                    @BusinessActionContextParameter(paramName = "productId") Long productId,
                    @BusinessActionContextParameter(paramName = "count") Integer count);

    //Confirm
    boolean commit(BusinessActionContext ctx);

    //Cancel
    boolean rollback(BusinessActionContext ctx);
}

package cn.tedu.tcc;

import cn.tedu.entity.Storage;
import cn.tedu.mapper.StorageMapper;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Component
public class StorageTccActionImpl implements StorageTccAction {

    @Autowired
    private StorageMapper storageMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long productId, Integer count) {
        Storage storage = storageMapper.selectById(productId);
        if (storage.getResidue() < count) {
            throw new RuntimeException("库存不足");
        }
        storageMapper.updateFrozen(productId, count);
        return true;
    }
    @Transactional
    @Override
    public boolean commit(BusinessActionContext ctx) {
        Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
        Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
        storageMapper.updateFrozenToUsed(productId, count);
        return true;
    }
    @Transactional
    @Override
    public boolean rollback(BusinessActionContext ctx) {
        Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
        Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
        storageMapper.updateFrozenToResidue(productId, count);
        return true;
    }
}

5.5 修改StorageServcieImpl

package cn.tedu.service;

import cn.tedu.mapper.StorageMapper;
import cn.tedu.tcc.StorageTccAction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StorageServiceImpl implements StorageService{

//    @Autowired
//    private StorageMapper storageMapper;

    @Autowired
    private StorageTccAction storageTccAction;

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

5.6 测试

  • 按顺序启动服务:
    Eureka
    Seata Server
    Easy Id Generator
    Storage
    Order

调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
查看控制台:
在这里插入图片描述

6. account添加TCC事务

6.1 修改resouce配置

在这里插入图片描述

6.2 修改AccoutMapper接口

package cn.tedu.mapper;

import cn.tedu.entity.Account;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.math.BigDecimal;

public interface AccountMapper extends BaseMapper<Account> {
    void decrease(Long userId, BigDecimal money);

    //查询 -- 使用继承方法

    //冻结
    void updateFrozen(Long userId, BigDecimal money);
    //冻结 --> 已使用
    void updateFrozenToUsed(Long userId, BigDecimal money);
    //冻结 --> 可用
    void updateFrozenResidue(Long userId, BigDecimal money);
}

6.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>

6.4 添加TCC接口和实现类

package cn.tedu.tcc;

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;

import java.math.BigDecimal;

@LocalTCC
public interface AccountTccAction {
    @TwoPhaseBusinessAction(name = "AccountTccAction")
    boolean prepare(BusinessActionContext ctx,
                    @BusinessActionContextParameter(paramName = "userId") Long userId,
                    @BusinessActionContextParameter(paramName = "money")BigDecimal money
                    );

    boolean commit(BusinessActionContext ctx);

    boolean rollback(BusinessActionContext ctx);
}

package cn.tedu.tcc;

import cn.tedu.entity.Account;
import cn.tedu.mapper.AccountMapper;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.CountDownLatch;

public class AccountTccActionImpl implements AccountTccAction {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long userId, BigDecimal money) {
        Account account = accountMapper.selectById(userId);
        //a.compareTo(b)  a大返回整数,a小返回负数,相同返回0
        if (account.getResidue().compareTo(money) < 0) {
            throw new RuntimeException("金额不足");
        }

        accountMapper.updateFrozen(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.updateFrozenResidue(userId, money);
        ResultHolder.removeResult(AccountTccAction.class, ctx.getXid());
        return true;
    }
}

6.5 修改AccountServiceImpl类

package cn.tedu.servie;

import cn.tedu.mapper.AccountMapper;
import cn.tedu.tcc.AccountTccAction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.math.BigDecimal;

@Service
public class AccountServiceImpl implements AccountService {
//    @Autowired
//    private AccountMapper accountMapper;

    @Autowired
    private AccountTccAction accountTccAction;

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

6.6 测试

  • 按顺序启动服务:
    Eureka
    Seata Server
    Easy Id Generator
    Storage
    Account
    Order

调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
观察 account 的控制台日志:
在这里插入图片描述
观察 storage 的控制台日志:
在这里插入图片描述
观察 Order 的控制台日志:
在这里插入图片描述

7. 幂等性控制

如果TC协调器,要求重复执行提交或回滚,多次操作,要和一次操作结果一样

  • 第一阶段:
    • 完成时,先保存一个第一阶段完成标记
  • 第二阶段:
    • 先检查标记,如果有则提交或回滚;反之则不提交

7.1 ResultHolder工具类

package cn.tedu.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);
        }
    }
}

7.2 修改StorageTccActionImpl

package cn.tedu.tcc;

import cn.tedu.entity.Storage;
import cn.tedu.mapper.StorageMapper;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Component
public class StorageTccActionImpl implements StorageTccAction {

    @Autowired
    private StorageMapper storageMapper;

    @Transactional
    @Override
    public boolean prepare(BusinessActionContext ctx, Long productId, Integer count) {
        Storage storage = storageMapper.selectById(productId);
        if (storage.getResidue() < count) {
            throw new RuntimeException("库存不足");
        }
        storageMapper.updateFrozen(productId, count);
        //第一阶段完成时保存标记
        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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_Solider

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

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

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

打赏作者

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

抵扣说明:

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

余额充值