分布式事务系列--SpringCloud整合byteTCC框架0.5.x版本2

2019-03-06 16:29:47 浏览数 (1)

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逻辑。

0 人点赞