1、Demo项目依赖

  • springboot-2.4.7
  • spring-cloud 2020.0.1
  • spring-cloud-alibaba 2.21.1
  • openfeign 3.0.1
  • seata 1.4.2
  • spring-cloud-starter-alibaba-nacos-discovery 2.21.1
  • mybatis-springboot-starter 2.1.2

    2、Demo案例设计

    本demo设计了一个转账交易的业务场景,分为两个部分,第一部分是本地调用取款服务,第二部分是远程调用存款服务,远程调用通过FeignClient实现,以nacos做注册中心,以seata支持分布式事务,使用本地配置。
    【QuickStart】Seata-TCC与springboot、Feign集成 - 图1

    3、Demo工程搭建

    搭建Springboot项目,并引入相关依赖,分别搭建2个项目demo01和demo02,并启动一个seata-server实例,启动一个nacos实例。

  • demo的配置文件application.yml如下: ```java server: port: 8081

spring: application: name: seata-demo-01 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://139.198.170.206:3306/seata-demo-bank01?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 username: root password: Xw159753. cloud: nacos: discovery: server-addr: 139.198.170.206:8848 group: SEATA_GROUP namespace: b0e7a06f-af32-436b-97c0-1639e7e03b49

seata: enabled: true application-id: ${spring.application.name} enable-auto-data-source-proxy: true tx-service-group: seata-demo-01-group registry: type: nacos nacos: server-addr: 139.198.170.206:8848 group: SEATA_GROUP namespace: b0e7a06f-af32-436b-97c0-1639e7e03b49 username: “” password: “” config: type: file file: name: file.conf

logging: level: io: seata: info

mybatis: mapperLocations: classpath:mapper/.xml configuration: map-underscore-to-camel-case: true

log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  1. - demoseata的配置文件file.conf如下:
  2. ```java
  3. transport {
  4. # tcp udt unix-domain-socket
  5. type = "TCP"
  6. #NIO NATIVE
  7. server = "NIO"
  8. #enable heartbeat
  9. heartbeat = true
  10. # the client batch send request enable
  11. enableClientBatchSendRequest = true
  12. #thread factory for netty
  13. threadFactory {
  14. bossThreadPrefix = "NettyBoss"
  15. workerThreadPrefix = "NettyServerNIOWorker"
  16. serverExecutorThread-prefix = "NettyServerBizHandler"
  17. shareBossWorker = false
  18. clientSelectorThreadPrefix = "NettyClientSelector"
  19. clientSelectorThreadSize = 1
  20. clientWorkerThreadPrefix = "NettyClientWorkerThread"
  21. # netty boss thread size,will not be used for UDT
  22. bossThreadSize = 1
  23. #auto default pin or 8
  24. workerThreadSize = "default"
  25. }
  26. shutdown {
  27. # when destroy server, wait seconds
  28. wait = 3
  29. }
  30. serialization = "seata"
  31. compressor = "none"
  32. }
  33. service {
  34. #transaction service group mapping
  35. #该配置项要与seata-server的cluster配置项的值一样
  36. vgroupMapping.seata-demo-01-group = "bank-demo"
  37. #only support when registry.type=file, please don't set multiple addresses
  38. default.grouplist = "127.0.0.1:8091"
  39. #degrade, current not support
  40. enableDegrade = false
  41. #disable seata
  42. disableGlobalTransaction = false
  43. }
  44. client {
  45. rm {
  46. asyncCommitBufferLimit = 10000
  47. lock {
  48. retryInterval = 10
  49. retryTimes = 30
  50. retryPolicyBranchRollbackOnConflict = true
  51. }
  52. reportRetryCount = 5
  53. tableMetaCheckEnable = false
  54. reportSuccessEnable = false
  55. }
  56. tm {
  57. commitRetryCount = 5
  58. rollbackRetryCount = 5
  59. }
  60. undo {
  61. dataValidation = true
  62. logSerialization = "jackson"
  63. logTable = "undo_log"
  64. }
  65. log {
  66. exceptionRate = 100
  67. }
  68. }

4、Demo业务场景开发

采用经典的springMVC架构,在AccountCotroller中暴露服务接口,在Service中编写服务逻辑,在mapper中进行数据库交互。重点不同的是,需要开发seata-tcc相关的接口。

  • 工程结构如下:

image.png

4.1、开发AccountTccAction接口和实现

  • AccountTccAction接口如下:

    1. @LocalTCC
    2. public interface AccountTccAction {
    3. /**
    4. * 取款
    5. * 使用 @BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext
    6. * @param businessActionContext BusinessActionContext 上下文对象,用来在两个阶段之间传递数据
    7. * @param accountId
    8. * @param money
    9. * @return
    10. */
    11. @TwoPhaseBusinessAction(name = "fetchBalanceTcc",commitMethod = "fetchBalanceCommit",rollbackMethod = "fetchBalanceRollback")
    12. public String fetchBalanceTry(BusinessActionContext businessActionContext,
    13. @BusinessActionContextParameter(paramName = "accountId") String accountId,
    14. @BusinessActionContextParameter(paramName = "money") int money);
    15. /**
    16. * 取款服务 二阶段提交
    17. * @param businessActionContext
    18. */
    19. public void fetchBalanceCommit(BusinessActionContext businessActionContext);
    20. /**
    21. * 取款服务 二阶段回滚
    22. * @param businessActionContext
    23. */
    24. public void fetchBalanceRollback(BusinessActionContext businessActionContext);
    25. }
  • AccountTccActionImpl实现类如下:

    1. @Service
    2. public class AccountTccActionImpl implements AccountTccAction {
    3. private static final Logger log = LoggerFactory.getLogger(AccountService.class);
    4. @Autowired
    5. AccountMapper accountMapper;
    6. @Autowired
    7. AccountServiceFeign accountServiceFeign;
    8. @Override
    9. public String fetchBalanceTry(BusinessActionContext businessActionContext, String accountId, int money) {
    10. Account account = accountMapper.getAccountInfoById(accountId);
    11. int balance = account.getBalance() - money;
    12. accountMapper.transformMoney(accountId,balance);
    13. return "取款成功,账户余额:"+balance;
    14. }
    15. @Override
    16. public void fetchBalanceCommit(BusinessActionContext businessActionContext) {
    17. //为了演示,仅打印日志,并未进行实际的业务二次提交操作
    18. String accountId = (String)businessActionContext.getActionContext("accountId");
    19. int money = (int) businessActionContext.getActionContext("money");
    20. log.info(">>>>>>>>>>>>>fetchBalanceCommit running, accountId:{}, money:{}",accountId,money);
    21. }
    22. @Override
    23. public void fetchBalanceRollback(BusinessActionContext businessActionContext) {
    24. //为了演示,仅打印日志,并未进行实际的业务回滚操作
    25. String accountId = (String)businessActionContext.getActionContext("accountId");
    26. int money = (int) businessActionContext.getActionContext("money");
    27. log.info(">>>>>>>>>>>>>fetchBalanceRollback running, accountId:{}, money:{}",accountId,money);
    28. }
    29. }

    4.2、开发远程调用的FeignClient

  • AccountServiceFeign如下: ```java @FeignClient(value = “seata-demo-02”, path = “/account”) @LocalTCC public interface AccountServiceFeign { /**

    • 存款
    • 使用 @BusinessActionContextParameter 注解标注的参数会自动注入到businessActionContext中
    • @param accountId
    • @param money
    • @return */ @RequestMapping(“/depositBalance”) @TwoPhaseBusinessAction(name = “remoteDepositBalance”,commitMethod = “remoteDepositBalanceCommit”,rollbackMethod = “remoteDepositBalanceRollback”) public String remoteDepositBalance(@RequestBody BusinessActionContext businessActionContext,
      1. @BusinessActionContextParameter(paramName = "accountId") @RequestParam("accountId")String accountId,
      2. @BusinessActionContextParameter(paramName = "money") @RequestParam("money")int money);
      @RequestMapping(“/depositBalanceCommit”) public Boolean remoteDepositBalanceCommit(@RequestBody BusinessActionContext businessActionContext); @RequestMapping(“/depositBalanceRollback”) public Boolean remoteDepositBalanceRollback(@RequestBody BusinessActionContext businessActionContext);

}

  1. <a name="ZPXiJ"></a>
  2. ## 4.3、在业务代码中调用转账服务,并开启全局事务
  3. - AccountService如下:
  4. ```java
  5. @Service
  6. public class AccountService {
  7. private static final Logger log = LoggerFactory.getLogger(AccountService.class);
  8. @Autowired
  9. AccountServiceFeign accountServiceFeign;
  10. @Autowired
  11. AccountTccAction accountTccAction;
  12. /**
  13. * 远程转账 使用LocalTcc模式
  14. * @param localAccountIdSrc
  15. * @param remoteAccountIdDest
  16. * @param money
  17. * @return
  18. */
  19. @GlobalTransactional
  20. public String remoteTransformMoneyTcc(String localAccountIdSrc,String remoteAccountIdDest, int money){
  21. //本地取款
  22. accountTccAction.fetchBalanceTry(null,localAccountIdSrc,money);
  23. //远程调用存款
  24. String result = accountServiceFeign.remoteDepositBalance(null,remoteAccountIdDest,money);
  25. return result;
  26. }

4.4、在Controller中暴露转账接口

  • AccountController如下:

    1. @RestController
    2. @RequestMapping(value="/account")
    3. public class AccountController {
    4. @Autowired
    5. AccountService accountService;
    6. /**
    7. * 跨行转账,使用Tcc模式
    8. * @param localAccountIdSrc
    9. * @param remoteAccountIdDest
    10. * @param money
    11. * @return
    12. */
    13. @RequestMapping("/remoteTransformMoneyTcc")
    14. public String remoteTransformMoneyTcc(@RequestParam("localAccountIdSrc") String localAccountIdSrc,
    15. @RequestParam("remoteAccountIdDest") String remoteAccountIdDest,
    16. @RequestParam("money")int money){
    17. return accountService.remoteTransformMoneyTcc(localAccountIdSrc,remoteAccountIdDest,money);
    18. }

    4.5、远程存款服务开发

  • RemoteAccountController如下: ```java @RestController @RequestMapping(value=”/account”) public class AccountController { private static final Logger log = LoggerFactory.getLogger(AccountController.class); @Autowired AccountService accountService;

    /**

    • 存款 一阶段try
    • @param accountId
    • @param money
    • @return */ @RequestMapping(“/depositBalance”) public String depositBalance(@RequestBody BusinessActionContext businessActionContext,
      1. @RequestParam("accountId") String accountId,
      2. @RequestParam("money")int money){
      String xid = (String)businessActionContext.getXid(); log.info(“>>>>>>>>>>>>>>>>>>depositBalanceCommit running, xid:{}”,xid); return accountService.depositBalance(accountId,money); } /**
    • 存款 二阶段提交
    • @param businessActionContext
    • @return / @RequestMapping(“/depositBalanceCommit”) public Boolean depositBalanceCommit(@RequestBody BusinessActionContext businessActionContext){ String accountId = (String)businessActionContext.getActionContext(“accountId”); int money = (int)businessActionContext.getActionContext(“money”); String xid = (String)businessActionContext.getXid(); log.info(“>>>>>>>>>>>>>>>>>>depositBalanceCommit running, accountId:{}, money:{}, xid:{}”,accountId,money,xid); return true; } /*
    • 存款 二阶段回滚
    • @param businessActionContext
    • @return */ @RequestMapping(“/depositBalanceRollback”) public Boolean depositBalanceRollback(@RequestBody BusinessActionContext businessActionContext){ String accountId = (String)businessActionContext.getActionContext(“accountId”); int money = (int)businessActionContext.getActionContext(“money”); String xid = (String)businessActionContext.getXid(); log.info(“>>>>>>>>>>>>>>>>>>depositBalanceRollback running, accountId:{}, money:{}, xid:{}”,accountId,money,xid); return true; }

} ```

5、总结

  • @LocalTcc注解必须使用在interface上,不能直接在class上使用,否则不生效
  • BusinessActionContext是seata-tcc模式的上下文,相关业务参数和分布式事务的参数都可以通过该上下文传递
  • 使用@BusinessActionContextParameter(paramName = “accountId”)注解标注的参数,seata会自动注入到上下文BusinessActionContext中
  • 需要使用@GlobalTransactional注解开启全局事务,否则不生效