Tcc模式多用于非关系型数据库,由于非关系型数据库是没有事务这个概念的,所以需要程序员自定义代码。实现三个方法。下面是三个方法。

Try:资源的检测和预留
Confirm:业务执行和提交
Cancel:预留资源释放
补充:
TCC是和AT和TC兼容的。
需要考虑业务悬挂情况:
对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂。解决办法:执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂
需要考虑幂等性的情况:
微服务因为某种原因失联了,客户端发送n次信息,解决方案:只计算一次
需要考虑空回滚的情况:
在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。给TC发命令告诉他已经完成了,要不然会一直回滚。
解决办法:执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。

接下来将开始做示例如何基于mybatis搭建项目,实现重构三种方法实现TCC。
一、项目搭建
1.1例子,建表
1)CREATE TABLE `tb_account_freeze` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` int(11) NOT NULL,
`freeze_money` int(11) UNSIGNED NULL DEFAULT 0,
`state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
);
1.2创建实体类
@Data
@TableName("tb_account_freeze")//绑定上面的表
public class AccountFreeze {
@TableId(type = IdType.INPUT)
private String xid;
private Integer userId;
private Integer freezeMoney;
private Integer state;
public static abstract class State {
public final static int TRY = 0;
public final static int CONFIRM = 1;
public final static int CANCEL = 2;
}
}
1.3Mapper接口
public interface AccountFreezeMapper extends BaseMapper<AccountFreeze> {
}
1.4声明TCC接口
@LocalTCC
public interface AccountTCCService {
/**
* 一阶段:try 预留
* @TwoPhaseBusinessAction 定义 try confirm cancel 方法名
*/
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")//@BusinessActionContextParameter这个注解是把对应的参数存储到类似ThreadLocalt的空间中,供BusinessActionContext注解去获取。
void deduct(@BusinessActionContextParameter(paramName = "userId") Integer userId,
@BusinessActionContextParameter(paramName = "money") Integer money);
/**
* 二阶段:confirm 提交
* @param ctx 可以获得 @BusinessActionContextParameter参数和xid
*
*/
Boolean confirm(BusinessActionContext ctx);
/**
* 二阶段:cancel 回滚
* @param ctx 可以获得 @BusinessActionContextParameter参数和xid
*
*/
Boolean cancel(BusinessActionContext ctx);
}
1.5实现TCC接口
@Service
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
public void deduct(Integer userId, Integer money) {
// 0.获取事务id
String xid = RootContext.getXID();
// 解决业务悬挂
AccountFreeze oldFreeze = freezeMapper.selectById(xid);
if (oldFreeze != null) {
return;
}
// 1.扣除可用余额
accountMapper.deduct(userId, money);
// 2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setXid(xid);
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// 1.获取xid
String xid = ctx.getXid();
// 2.删除冻结记录
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
@Transactional
public boolean cancel(BusinessActionContext ctx) {
// 1.查询冻结记录
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
// 解决空回滚
if (freeze == null) {
int userId = Integer.parseInt(ctx.getActionContext("userId").toString());
freeze = new AccountFreeze();
freeze.setXid(xid);
freeze.setUserId(userId);
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
freezeMapper.insert(freeze);
return true;
}
// 解决幂等性
if (freeze.getState() == AccountFreeze.State.CANCEL) {
return true;
}
// 2.恢复账户金额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 3.更新冻结记录
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}