6.tcc业务逻辑编写
在使用tcc框架处理分布式事务时,需要我们自己来编写tcc业务代码。这里演示一个简单的加钱的操作。
一个tcc操作,分为try,confirm,cancel三个操作。
根据创建的company表,可以看到公司表有一个money金额字段,还有个frozen字段,在业务简单时,我们可以借助这个字段来实现tcc;如果业务复杂,修改多个字段时,我们可以不要这个字段,tcc的每一步,都直接操作目标字段。
6.1mappper.xml编写
业务的目标操作是:给money加钱。
这里分为三条sql,分别是:
1.try操作
这里先添加在frozen字段,暂不操作money字段。(如果业务对操作资源需要做判断,比如是不是够减,那就需要操作money字段)
2.confirm操作
如果try操作成功了,且各链路服务都没有问题,框架认为走confirm分支时,那我们就认为网络和服务等是没有问题的,本地事务就执行这个confirm操作,操作money字段,同时,把fronzen字段清零。
3.cancel操作
如果链路上有环节出现异常,那么就会进入cancel分支,执行cancel逻辑。
代码语言:javascript复制<?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="com.java4all.dao.CompanyDao">
<resultMap id="resMap" type="com.java4all.entity.Company">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="id" jdbcType="INTEGER" property="id" />
<result column="money" jdbcType="DECIMAL" property="money" />
<result column="frozen" jdbcType="DECIMAL" property="frozen" />
</resultMap>
<!--try逻辑-->
<update id="increaseMoney">
UPDATE company SET frozen = frozen #{money}
WHERE id = #{id}
</update>
<!--confirm逻辑-->
<update id="confirmIncreaseMoney">
UPDATE company SET money = money #{money},frozen = frozen - #{money}
WHERE id = #{id}
</update>
<!--cancel逻辑-->
<update id="cancelIncreaseMoney">
UPDATE company SET frozen = frozen - #{money}
WHERE id = #{id}
</update>
</mapper>
6.2dao编写
代码语言:javascript复制package com.java4all.dao;
import java.math.BigDecimal;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
/**
* description:
*
* @author wangzhongxiang
* @date 2019/2/14 14:39
*/
@Repository
public interface CompanyDao {
int increaseMoney(@Param("id") Integer id,@Param("money") BigDecimal money);
int confirmIncreaseMoney(@Param("id") Integer id,@Param("money") BigDecimal money);
int cancelIncreaseMoney(@Param("id") Integer id,@Param("money") BigDecimal money);
}
6.3service编写
接口里就一个方法,加钱的方法。
代码语言:javascript复制package com.java4all.service;
import java.math.BigDecimal;
/**
* description:
*
* @author wangzhongxiang
* @date 2019/2/14 14:38
*/
public interface CompanyService {
int increaseMoney(Integer id, BigDecimal money);
}
6.4serviceImpl编写
接口的加钱方法,对应过来是有3个实现方法的,confirm和cancel的逻辑分别写在对应的实现类中,try逻辑,我们可以直接写在controller中,也可以单独的写在一个实现类中,我这里写在实现类中,这样会更加清晰,实际开发中也推荐这样写,功能非常清晰,controller不应该承载业务逻辑,业务都在service接口的实现类中。
1.CompanyServiceImpl
CompanyServiceImpl 中对应的是try逻辑。
代码语言:javascript复制package com.java4all.service.impl;
import com.java4all.dao.CompanyDao;
import com.java4all.service.CompanyService;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* description:
* tcc try逻辑
* 1.@Service("companyServiceImpl")这里必须指定此bean名称,tcc过程依靠此名称区分执行tcc中哪个逻辑
* 2.参与tcc的方法必须添加@Transactional注解
* 3.建议tcc每个步骤,方法执行后添加日志,方便问题排查
*
* @author wangzhongxiang
* @date 2019/2/14 15:06
*/
@Slf4j
@Service("companyServiceImpl")
public class CompanyServiceImpl implements CompanyService{
@Autowired
private CompanyDao companyDao;
@Override
@Transactional
public int increaseMoney(Integer id, BigDecimal money) {
int line = companyDao.increaseMoney(id, money);
log.info("【try】 increaseMoney: id = " id ",money =" money);
return line;
}
}
2.CompanyServiceConfirm
CompanyServiceConfirm中对应的是confirm逻辑。
代码语言:javascript复制package com.java4all.service.impl;
import com.java4all.dao.CompanyDao;
import com.java4all.service.CompanyService;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* description:
* tcc confirm逻辑
* 1.@Service("companyServiceConfirm")这里必须指定此bean名称,tcc过程依靠此名称区分执行tcc中哪个逻辑
* 2.参与tcc的方法必须添加@Transactional注解
* 3.建议tcc每个步骤,方法执行后添加日志,方便问题排查
*
* @author wangzhongxiang
* @date 2019/2/14 15:06
*/
@Slf4j
@Service("companyServiceConfirm")
public class CompanyServiceConfirm implements CompanyService{
@Autowired
private CompanyDao companyDao;
@Override
@Transactional
public int increaseMoney(Integer id, BigDecimal money) {
int line = companyDao.confirmIncreaseMoney(id, money);
log.info("【confirm】 increaseMoney: id = " id ",money =" money);
return line;
}
}
CompanyServiceCancel
CompanyServiceCancel中是cancel的逻辑。
代码语言:javascript复制package com.java4all.service.impl;
import com.java4all.dao.CompanyDao;
import com.java4all.service.CompanyService;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* description:
* tcc cancel逻辑
* 1.@Service("companyServiceCancel")这里必须指定此bean名称,tcc过程依靠此名称区分执行tcc中哪个逻辑
* 2.参与tcc的方法必须添加@Transactional注解
* 3.建议tcc每个步骤,方法执行后添加日志,方便问题排查
*
* @author wangzhongxiang
* @date 2019/2/14 15:06
*/
@Slf4j
@Service("companyServiceCancel")
public class CompanyServiceCancel implements CompanyService{
@Autowired
private CompanyDao companyDao;
@Override
@Transactional
public int increaseMoney(Integer id, BigDecimal money){
int line = companyDao.cancelIncreaseMoney(id, money);
log.info("【cancel】 increaseMoney: id = " id ",money =" money);
return line;
}
}
6.5controller编写
controller中有一个需要注意的点,就是tcc的核心实现@Compensable注解。@Compensable注解有3三参数,这里引用下作者提供的文档中对此的说明:
通过@Compensable注解定义的service为可补偿型service。@Compensable注解需要定义三个参数:
1)interfaceClass,必需。该值用于指定confirm/cancel针对的业务接口,该接口同时被用于校验confirm/cancel实现类。confirm/cancel实现类如果没有实现该业务接口则会被认为无效;
2)confirmableKey,可选。该值用于指定confirm实现类在容器中的beanId,若没有confirm逻辑则不必指定;
3)cancellableKey,可选。该值用于指定cancel实现类在容器中的beanId,若没有cancel逻辑则不必指定;注意:若try阶段执行了写操作则必须有相应的取消逻辑;
4)服务提供方Controler必须添加@Compensable注解;
5)针对一个特定的可补偿型服务接口,业务系统提供的Try、Confirm、Cancel三个实现类,其Try实现类必须定义@Compensable注解,而Confirm、Cancel实现类则不能定义Compensable注解;
文档地址:https://github.com/liuyangming/ByteTCC/wiki/User-Guide-0.5.x
代码语言:javascript复制package com.java4all.controller;
import com.java4all.service.CompanyService;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.bytesoft.compensable.Compensable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author wangzhongxiang
* @date 2019/2/14 14:38
*/
@Compensable(
interfaceClass = CompanyService.class,
confirmableKey = "companyServiceConfirm",
cancellableKey = "companyServiceCancel")
@Slf4j
@RestController
@RequestMapping("company")
public class CompanyController {
/**
* 在调用此接口时,首先执行的是tcc中的try,
* 因此,我们需要明确指定引入的究竟是此接口的哪一个实现类,
* 首先执行try,try逻辑写在companyServiceImpl类中,我们就需要明确指定,
* @Autowired private CompanyService companyService
* 上述这种写法是不可以的,无法识别究竟是哪一个实现类,因此实现类,需要明确指定bean名称
* @Compensable注解扫描时,在哪一种状态执行哪个实现类方法,是
* */
@Autowired
private CompanyService companyServiceImpl;
@PostMapping("increaseMoney")
@Transactional
public void increaseMoney(Integer id,BigDecimal money){
int line = companyServiceImpl.increaseMoney(id, money);
log.info("修改行数为:" line);
}
}
- description:
- try逻辑有2种写法:
- 1.直接写在controller中
- 在controller中直接引入dao层,调用dao层方法,
- 此时,@Compensable添加在controller中;
- 2.写在接口的实现类中
- 此时,在controller中引入实现类时,需要明确指定bean名称。
- @Compensable注解,应该仅仅添加在controller中
- 如果仅仅添加在try逻辑的实现类上,那么仅仅会执行try逻辑,cc逻辑不会执行;
- 如果try逻辑的实现类和controller都添加,那么cc逻辑会执行两遍。
- 在官方文档中,明确指出了:必须在try的实现类添加@Compensable,而controller建议添加。
- 此说法经过验证有问题。可能是之前版本遗留问题,但是文档没有及时更新。
框架会根据每一步操作的结果来控制事务走向,选择是confirm还是cancel逻辑。