TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
图片
阶段一(Try):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30
图片
图片
此时,总金额 = 冻结金额 + 可用金额,数量依然是100不变,事务直接提交无需等待其它事务。
阶段二(Confirm) :假如要提交,则冻结金额扣减30
图片
确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了,此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70
阶段二(Cancel):如果要回滚,则冻结金额扣减30,可用余额增加30
图片
需要回滚,那么就要释放冻结金额,恢复可用金额
图片
配置和依赖参考之前《利用Nacos实现Seata事务模式(XA与AT)的快速配置与灵活切换》即可
bank3:
声明TCC接口@LocalTCCpublic interface AccountInTcc { @TwoPhaseBusinessAction(name = "prepareDeductMoney", commitMethod = "commitDeductMoney", rollbackMethod = "rollbackDeductMoney") boolean prepareDeductMoney(BusinessActionContext businessActionContext, @BusinessActionContextParameter(paramName = "accountNo")String accountNo, @BusinessActionContextParameter(paramName = "amount")Double amount); /** * 提交扣款 * 二阶段confirm确认方法、可以另命名,但要保证与commitMethod一致 */ boolean commitDeductMoney(BusinessActionContext businessActionContext); /** * 回滚扣款 * 二阶段回滚方法,要保证与rollbackMethod一致 */ boolean rollbackDeductMoney(BusinessActionContext businessActionContext);}
具体实现:
@Componentpublic class AccountInTccImpl implements AccountInTcc { @Autowired private AccountInfoMapper accountInfoMapper; @Transactional @Override public boolean prepareDeductMoney(BusinessActionContext businessActionContext, String accountNo, Double amount) { String xid = businessActionContext.getXid(); // 幂等性判断 if (TccActionResultWrap.hasPrepareResult(xid)) { return true; } // 避免空悬挂,已经执行过回滚了就不能再预留资源 if (TccActionResultWrap.hasRollbackResult(xid) || TccActionResultWrap.hasCommitResult(xid)) { return false; } // 预留资源 boolean result = accountInfoMapper.prepareDeductMoney(accountNo,amount) > 0; // 记录执行结果,以便回滚时判断是否是空回滚 TccActionResultWrap.prepareSuccess(xid); System.out.println("============prepare=============="); return result; } // 保证提交逻辑的原子性 @Transactional @Override public boolean commitDeductMoney(BusinessActionContext businessActionContext) { String xid = businessActionContext.getXid(); // 幂等性判断 if (TccActionResultWrap.hasCommitResult(xid)) { return true; } Map<String, Object> actionContext = businessActionContext.getActionContext(); String accountNo = (String) actionContext.get("accountNo"); BigDecimal amount = (BigDecimal) actionContext.get("amount"); // 执行提交操作,扣除预留款 boolean result = accountInfoMapper.commitDeductMoney(accountNo,amount.doubleValue()) > 0; // 清除预留结果 TccActionResultWrap.removePrepareResult(xid); // 设置提交结果 TccActionResultWrap.commitSuccess(xid); System.out.println("============commit=============="); return result; } @Transactional @Override public boolean rollbackDeductMoney(BusinessActionContext businessActionContext) { String xid = businessActionContext.getXid(); // 幂等性判断 if (TccActionResultWrap.hasRollbackResult(xid)) { return true; } // 没有预留资源结果,回滚不做任何处理; if (!TccActionResultWrap.hasPrepareResult(xid)) { // 设置回滚结果,防止空回滚 TccActionResultWrap.rollbackSuccess(xid); return true; } // 执行回滚 Map<String, Object> actionContext = businessActionContext.getActionContext(); String accountNo = (String) actionContext.get("accountNo"); BigDecimal amount = (BigDecimal) actionContext.get("amount"); boolean result = accountInfoMapper.rollbackDeductMoney(accountNo,amount.doubleValue()) > 0; // 清除预留结果 TccActionResultWrap.removePrepareResult(xid); // 设置回滚结果 TccActionResultWrap.rollbackSuccess(xid); System.out.println("============rollback=============="); return result; }}
业务层:
@Autowired private AccountInTcc accountInTcc; @Override public Boolean deductMoney(String accountNo, Double amount) { return accountInTcc.prepareDeductMoney(null,accountNo,amount); }
参数中的BusinessActionContext不需要开发人员自己传递,直接给null即可,Seata会自动处理。
mapper:
@Update("update account_info set account_balance = account_balance - #{amount}, frozen_money = frozen_money + #{amount} where account_no = #{accountNo} and account_balance >= #{amount}") int prepareDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount); @Update("update account_info set frozen_money = frozen_money - #{amount} where account_no = #{accountNo}") int commitDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount); @Update("update account_info set account_balance = account_balance + #{amount}, frozen_money = frozen_money - #{amount} where account_no = #{accountNo}") int rollbackDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount);
bank4服务调用:
@GlobalTransactional @Override public Boolean addMoney(String accountNo, Double amount) { String result = bank3Client.deduct(amount); if("true".equalsIgnoreCase(result)){ Boolean flag = baseMapper.addMoney(accountNo,amount) > 0; if(amount != 30 ) throw new RuntimeException("bank4 make exception amount != 30"); return flag; } return false; }
TCC的优点:
TCC的缺点:
图片
执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。
执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂。
本文链接:http://www.28at.com/showinfo-26-70394-0.html借助Nacos高效配置与实践Seata事务的TCC模式
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。