微服务框架
【SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】
分布式事务
文章目录
- 微服务框架
 - 分布式事务
 - 38 动手实践
 - 38.8 案例实现TCC 模式【实现】
 - 38.8.1 声明TCC 接口
 
38 动手实践
38.8 案例实现TCC 模式【实现】
38.8.1 声明TCC 接口
TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明,语法如下:

直接试试
在account-service 中
新创建接口
package cn.itcast.account.service;
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;
/**
 * ClassName: AccountTCCService
 * date: 2022/11/7 9:53
 *
 * @author DingJiaxiong
 */
@LocalTCC
public interface AccountTCCService {
    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money") int money);
    boolean confirm(BusinessActionContext ctx);
    boolean cancel(BusinessActionContext ctx);
}
 
OK,这样T、C、C 就有了
创建新的 “冻结表 ”
老师已经给了资料

将其导入到微服务使用的 数据库 seata-demo 中

OK
其实两个实体类,虎哥 都写好了
账户表

冻结表

mapper 也写好了【两个】
冻结表的数据层接口

账户表的数据层接口

OK,实现TCC 的业务层接口【就我们最开始写那个】
package cn.itcast.account.service.impl;
import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * ClassName: AccountTCCServiceImpl
 * date: 2022/11/7 10:03
 *
 * @author DingJiaxiong
 */
@Slf4j
@Service
public class AccountTCCServiceImpl implements AccountTCCService {
    //接口注入
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper freezeMapper;
    //try方法【资源检测】
    @Override
    @Transactional
    public void deduct(String userId, int money) {
        //0. 获取事务id
        String xid = RootContext.getXID();
        //1. 判断freeze 中是否有冻结记录,如果有,则一定是cancel 执行过,需要拒绝业务
        AccountFreeze oldFreeze = freezeMapper.selectById(xid);
        if (oldFreeze != null){
            //cancel 执行过,需要拒绝业务
            return;
        }
        //1. 扣减可用余额
        accountMapper.deduct(userId,money);
        //2. 记录冻结金额、事务状态
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(AccountFreeze.State.TRY);
        freeze.setXid(xid);
        freezeMapper.insert(freeze);
    }
    @Override
    public boolean confirm(BusinessActionContext ctx) {
        //1. 获取事务id
        String xid = ctx.getXid();
        //2. 根据id删除冻结记录
        int count = freezeMapper.deleteById(xid);
        return count == 1;
    }
    @Override
    public boolean cancel(BusinessActionContext ctx) {
        //0. 查询冻结记录
        String xid = ctx.getXid();
        String userId = ctx.getActionContext("userId").toString();
        AccountFreeze freeze = freezeMapper.selectById(xid);
        //1. 空回滚的判断,判断freeze 是否为null,为null证明try 没执行,需要空回滚
        if (freeze == null) {
            //证明try 没执行,需要空回滚
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            freezeMapper.insert(freeze);
            return true;
        }
        //2. 幂等判断
        if (freeze.getState() == AccountFreeze.State.CANCEL){
            // 已经处理过一次cancel 了,无需重复处理
            return true;
        }
        //1. 恢复可用余额
        accountMapper.refund(freeze.getUserId(),freeze.getFreezeMoney());
        //2. 将冻结金额清零、状态改为cancel
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        //更新
        int count = freezeMapper.updateById(freeze);
        return count == 1;
    }
}
 
我超,真的费程序员
修改一下控制器

OK,重启服务开始测试

先记录一下数据库现在的状态

当前账户余额 是 400

冻结表也是空的

订单当前也是两条

库存为 6
先来个正确的测试

直接send

哇靠,挂掉了
看看日志

不能连接到8091 ,seata 服务挂了?

重启一下seata 服务,和三个微服务


再试一次

OK,成功 了,返回 了订单id
再次查看数据库

新增了一条订单

余额减少了 200

库存余额 也减少 了2

freeze 依然是空的,因为confirm 后会删除记录
现在来一次异常情况

直接send

OK,再次查看数据库【预期会回滚】

订单依然是3 个

账户余额 没有变化

库存也没有减少

OK,冻结表有东西了
IDEA 控制台

这就是TCC 模式的实现













![[附源码]Nodejs计算机毕业设计基于web的企业人事管理系统Express(程序+LW)](https://img-blog.csdnimg.cn/e5611d62c87349a681449db63b377c61.png)





