当前位置: 首页 > news >正文

南昌专业网站建设公司深圳网站建设公

南昌专业网站建设公司,深圳网站建设公,长沙网站开发培训,鄂尔多斯市建设网站一、前言 通过以下系列章节#xff1a; docker-compose 实现Seata Server高可用部署 | Spring Cloud 51 Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52 Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53 Seata XA 模式理论学习、使用…一、前言 通过以下系列章节 docker-compose 实现Seata Server高可用部署 | Spring Cloud 51 Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52 Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53 Seata XA 模式理论学习、使用及注意事项 | Spring Cloud54 我们对Seata及其AT事务模式、XA事务模式的理论、使用有了深入的了解今天继续对Seata的TCC事务模式进行理论学习并区别与官网我们利用openfeign进行生产级示例搭建降低入门难度。 理论部分来自Seata官网http://seata.io/zh-cn/docs/dev/mode/tcc-mode.html 二、整体机制 回顾前面章节的学习一个分布式的全局事务整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的分支事务要满足 两阶段提交 的模型要求即需要每个分支事务都具备自己的 一阶段 prepare 行为二阶段 commit 或 rollback 行为 上面这个流程中一共涉及到了三个方法prepare、commit 以及 rollback这三个方法都完全是用户自定义的方法都是需要我们自己来实现的。相较于 AT 事务模式 TCC 这种模式其实是不依赖于底层数据库的事务支持的。 三、示例说明 这是一个商品下单的案例一共有四个服务和一个公共模块 account-tcc账户服务可以查询/修改用户的账户信息order-tcc订单服务可以下订单。storage-tcc仓储服务可以查询/修改商品的库存数量。bussiness-tcc业务服务用户下单操作将在这里完成。common-tcc公共模块包含实体类、openfeign接口、统一异常处理等。 具体业务吊牌逻辑如下 四、数据库设计 4.1 账户表 account-tcc 账户服务对应账户表t_account -- ---------------------------- -- Table structure for t_account -- ---------------------------- DROP TABLE IF EXISTS t_account; CREATE TABLE t_account (id bigint NOT NULL AUTO_INCREMENT,user_id varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT 用户ID,money decimal(10, 2) NULL DEFAULT 0.00 COMMENT 账户余额,freeze_money decimal(10, 2) NULL DEFAULT 0.00 COMMENT 冻结金额,PRIMARY KEY (id) USING BTREE ) ENGINE InnoDB AUTO_INCREMENT 1 CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci ROW_FORMAT Dynamic;-- ---------------------------- -- Records of t_account -- ---------------------------- INSERT INTO t_account VALUES (1, user1, 500.00, 0.00);SET FOREIGN_KEY_CHECKS 1; 4.2 仓储表 storage-tcc 仓储服务对应账户表t_storage -- ---------------------------- -- Table structure for t_storage -- ---------------------------- DROP TABLE IF EXISTS t_storage; CREATE TABLE t_storage (id bigint NOT NULL AUTO_INCREMENT,commodity_code varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,count int NULL DEFAULT 0,freeze_count int NULL DEFAULT 0,PRIMARY KEY (id) USING BTREE,UNIQUE INDEX commodity_code(commodity_code ASC) USING BTREE ) ENGINE InnoDB AUTO_INCREMENT 1 CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci ROW_FORMAT Dynamic;-- ---------------------------- -- Records of t_storage -- ---------------------------- INSERT INTO t_storage VALUES (1, iphone, 6, 0);SET FOREIGN_KEY_CHECKS 1;4.3 订单表 order-tcc 订单服务对应账户表t_order -- ---------------------------- -- Table structure for t_order -- ---------------------------- DROP TABLE IF EXISTS t_order; CREATE TABLE t_order (id bigint NOT NULL AUTO_INCREMENT,user_id varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,commodity_code varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,count int NULL DEFAULT 0,money decimal(10, 2) NULL DEFAULT 0.00,PRIMARY KEY (id) USING BTREE ) ENGINE InnoDB AUTO_INCREMENT 9 CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci ROW_FORMAT Dynamic;五、示例搭建 5.1 项目总体结构 5.2 common-tcc 搭建 5.2.1 实体类 com/gm/seata/openfeign/entity/Account.java import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal;Data TableName(t_account) public class Account {TableId(type IdType.ASSIGN_ID)private long id;private String userId;private BigDecimal money;private BigDecimal freezeMoney; }com/gm/seata/openfeign/entity/Order.java import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal;Data TableName(t_order) public class Order {TableId(type IdType.ASSIGN_ID)private long id;private String userId;private String commodityCode;private int count;private BigDecimal money; } com/gm/seata/openfeign/entity/Storage.java import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal;Data TableName(t_order) public class Order {TableId(type IdType.ASSIGN_ID)private long id;private String userId;private String commodityCode;private int count;private BigDecimal money; } 5.2.2 feign接口 com/gm/seata/openfeign/feign/AccountServiceApi.java import com.gm.seata.openfeign.entity.Account; import com.gm.seata.openfeign.util.R; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal;FeignClient(value account-tcc) public interface AccountServiceApi {/*** 扣除账户余额* param userId* param money* return*/RequestMapping(value deduct, method RequestMethod.GET)RAccount deduct(RequestParam(userId) String userId, RequestParam(money) BigDecimal money); }com/gm/seata/openfeign/feign/OrderServiceApi.java import com.gm.seata.openfeign.util.R; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;FeignClient(value order-tcc) public interface OrderServiceApi {/*** 创建订单* param userId* param commodityCode* param count* return*/RequestMapping(value createOrder, method RequestMethod.GET)RBoolean createOrder(RequestParam(userId) String userId, RequestParam(commodityCode) String commodityCode, RequestParam(count) Integer count); }com/gm/seata/openfeign/feign/StorageServiceApi.java import com.gm.seata.openfeign.entity.Storage; import com.gm.seata.openfeign.util.R; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;FeignClient(value storage-tcc) public interface StorageServiceApi {/*** 扣除库存* param commodityCode* param count* return*/RequestMapping(value deduct, method RequestMethod.GET)RStorage deduct(RequestParam(commodityCode) String commodityCode, RequestParam(count) Integer count); }5.2.3 feign 微服务调用异码处理 com/gm/seata/openfeign/handle/FeignErrorDecoder.java import com.alibaba.fastjson.JSONObject; import com.gm.seata.openfeign.util.ErrorEnum; import feign.Response; import feign.RetryableException; import feign.Util; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration;import java.nio.charset.Charset;Slf4j Configuration public class FeignErrorDecoder extends ErrorDecoder.Default {Overridepublic Exception decode(String methodKey, Response response) {try {// 可以自定义一些逻辑String message Util.toString(response.body().asReader(Charset.forName(utf8)));JSONObject jsonObject JSONObject.parseObject(message);int code jsonObject.getInteger(code);ErrorEnum errorEnum ErrorEnum.getEnumByCode(code);// 包装成自己自定义的异常return new RuntimeException(String.valueOf(errorEnum.getCode()));} catch (Exception e) {log.error(非已知异常, e.getMessage(), e);}Exception exception super.decode(methodKey, response);// 如果是RetryableException则返回继续重试if (exception instanceof RetryableException) {return exception;}return new RuntimeException(String.valueOf(ErrorEnum.UNKNOWN_EXCEPTION.getCode()));} }5.2.4 Controller 统一异常处理 com/gm/seata/openfeign/handle/GlobalBizExceptionHandler.java import com.gm.seata.openfeign.util.ErrorEnum; import com.gm.seata.openfeign.util.R; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus;; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 全局异常处理器*/ Slf4j Order(10000) RestControllerAdvice public class GlobalBizExceptionHandler {/*** 全局异常.** param e the e* return R*/ExceptionHandler(Exception.class)ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public R handleGlobalException(Exception e) {log.error(全局异常信息 ex{}, e.getMessage(), e);R r null;// 根据异常信息与已知异常进行匹配try {int code Integer.parseInt(e.getLocalizedMessage());ErrorEnum errorEnum ErrorEnum.getEnumByCode(code);if (errorEnum ! null) {r R.restResult(null, errorEnum.getCode(), errorEnum.getTitle());}} finally {if (e instanceof feign.FeignException) {ErrorEnum errorEnum ErrorEnum.UNKNOWN_EXCEPTION;r R.restResult(null, errorEnum.getCode(), errorEnum.getTitle());}if (r null) {r R.failed(e.getLocalizedMessage());}}return r;} }5.2.5 已知异常枚举类 com/gm/seata/openfeign/util/ErrorEnum.java import lombok.AllArgsConstructor; import lombok.Getter;Getter AllArgsConstructor public enum ErrorEnum {NO_SUCH_COMMODITY(3000, 无此商品),STORAGE_LOW_PREPARE(3001, 库存不足预扣库存失败),STORAGE_LOW_COMMIT(3002, 库存不足扣库存失败),NO_SUCH_ACCOUNT(4000, 无此账户),ACCOUNT_LOW_PREPARE(4001, 余额不足预扣款失败),ACCOUNT_LOW_COMMIT(4002, 余额不足扣款失败),UNKNOWN_EXCEPTION(9999, 远程方法调用异常);private final Integer code;private final String title;public static ErrorEnum getEnumByCode(int code) {for (ErrorEnum error : ErrorEnum.values()) {if (error.getCode().equals(code)) {return error;}}return null;} }5.2.6 响应信息结构体 com/gm/seata/openfeign/util/R.java import lombok.*; import lombok.experimental.Accessors; import java.io.Serializable;/*** 响应信息主体**/ ToString NoArgsConstructor AllArgsConstructor Accessors(chain true) public class RT implements Serializable {private static final long serialVersionUID 1L;/*** 成功标记*/private static final Integer SUCCESS 0;/*** 失败标记*/private static final Integer FAIL 1;GetterSetterprivate int code;GetterSetterprivate String msg;GetterSetterprivate T data;public static T RT ok() {return restResult(null, SUCCESS, null);}public static T RT ok(T data) {return restResult(data, SUCCESS, null);}public static T RT ok(T data, String msg) {return restResult(data, SUCCESS, msg);}public static T RT failed() {return restResult(null, FAIL, null);}public static T RT failed(String msg) {return restResult(null, FAIL, msg);}public static T RT failed(T data) {return restResult(data, FAIL, null);}public static T RT failed(T data, String msg) {return restResult(data, FAIL, msg);}public static T RT restResult(T data, int code, String msg) {RT apiResult new R();apiResult.setCode(code);apiResult.setData(data);apiResult.setMsg(msg);return apiResult;} }5.2.7 自动配置实现 在src/main/resources/META-INF/spring路径下新建文件org.springframework.boot.autoconfigure.AutoConfiguration.imports内容如下 com.gm.seata.openfeign.handle.GlobalBizExceptionHandler com.gm.seata.openfeign.handle.FeignErrorDecoder新建文件org.springframework.cloud.openfeign.FeignClient.imports内容如下 com.gm.seata.openfeign.feign.AccountServiceApi com.gm.seata.openfeign.feign.OrderServiceApi com.gm.seata.openfeign.feign.StorageServiceApi通过上述方式实现自动配置。 5.3 account-tcc 搭建 5.3.1 完整依赖 seata/openfeign-tcc/account-tcc/pom.xml ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentartifactIdopenfeign-tcc/artifactIdgroupIdcom.gm/groupIdversion0.0.1-SNAPSHOT/version/parentmodelVersion4.0.0/modelVersionartifactIdaccount-tcc/artifactIdpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdcom.gm/groupIdartifactIdcommon-tcc/artifactIdversion0.0.1-SNAPSHOT/version/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-bootstrap/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-loadbalancer/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!-- 注意一定要引入对版本要引入spring-cloud版本seata而不是springboot版本的seata--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId!-- 排除掉springcloud默认的seata版本以免版本不一致出现问题--exclusionsexclusiongroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactId/exclusionexclusiongroupIdio.seata/groupIdartifactIdseata-all/artifactId/exclusion/exclusions/dependency!-- 上面排除掉了springcloud默认色seata版本此处引入和seata-server版本对应的seata包--dependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactIdversion1.6.1/version/dependency!--dependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactId/dependency--dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactId/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3.1/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build /project5.3.2 配置文件 src/main/resources/bootstrap.yml server:port: 3011spring:application:name: artifactIdcloud:nacos:username: nacos.usernamepassword: nacos.passworddiscovery:server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.46:3306/seata-tcc?useUnicodetruecharacterEncodingUTF-8rewriteBatchedStatementstrueallowMultiQueriestrueserverTimezoneAsia/Shanghaiusername: rootpassword: 1qazWSX seata:# 是否开启spring-boot自动装配seata-spring-boot-starter 专有配置默认trueenabled: true# 是否开启数据源自动代理seata-spring-boot-starter专有配置默认会开启数据源自动代理可通过该配置项关闭enable-auto-data-source-proxy: false# 配置自定义事务组名称需与下方server.vgroupMapping配置一致程序会通过用户配置的配置中心去寻找service.vgroupMappingtx-service-group: mygroupconfig: # 从nacos配置中心获取client端配置type: nacosnacos:server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}group : DEFAULT_GROUPnamespace: a4c150aa-fd09-4595-9afe-c87084b22105dataId: seataServer.propertiesusername: nacos.usernamepassword: nacos.usernameregistry: # 通过服务中心通过服务发现获取seata-server服务地址type: nacosnacos:# 注客户端注册中心配置的serverAddr和namespace与Server端一致clusterName与Server端cluster一致application: seata-server # 此处与seata-server的application一致才能通过服务发现获取服务地址group : DEFAULT_GROUPserver-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}userName: nacos.usernamepassword: nacos.usernamenamespace: a4c150aa-fd09-4595-9afe-c87084b22105service:# 应用程序客户端会通过用户配置的配置中心去寻找service.vgroupMapping.[事务分组配置项]vgroup-mapping:# 事务分组配置项[mygroup]对应的值为TC集群名[default]与Seata-Server中的seata.registry.nacos.cluster配置一致mygroup : default# 全局事务开关默认false。false为开启true为关闭disable-global-transaction: falseclient:rm:report-success-enable: true management:endpoints:web:exposure:include: *logging:level:io.seata: debug# mybatis-plus配置控制台打印完整带参数SQL语句 mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 5.2.3 功能搭建 5.3.3.1 启动类 com/gm/seata/openfeign/AccountTCCApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;SpringBootApplication EnableDiscoveryClient public class AccountTCCApplication {public static void main(String[] args) {SpringApplication.run(AccountTCCApplication.class, args);} }5.3.3.2 Mapper类 com/gm/seata/openfeign/mapper/AccountMapper.java import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.gm.seata.openfeign.entity.Account; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select;Mapper public interface AccountMapper extends BaseMapperAccount {Select(SELECT * FROM t_account WHERE user_id #{userId} limit 1)Account getAccountByUserId(Param(userId) String userId); } 5.3.3.3 Service类 业务重点来了敲黑板 com/gm/seata/openfeign/service/AccountService.java import com.gm.seata.openfeign.entity.Account; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; import java.math.BigDecimal;LocalTCC public interface AccountService {/*** 执行资源检查及预业务操作*/// BusinessActionContextParameter 注解就是将对应的参数放入到 BusinessActionContext 中将来可以从 BusinessActionContext 中取出对应的参数。TwoPhaseBusinessAction(name accountService, commitMethod commit, rollbackMethod rollback)Account prepare(BusinessActionContext actionContext, BusinessActionContextParameter(paramName userId) String userId, BusinessActionContextParameter(paramName money) BigDecimal money);/*** 全局事物进行提交*/boolean commit(BusinessActionContext actionContext);/*** 全局事务进行回滚*/boolean rollback(BusinessActionContext actionContext); }首先接口的定义上需要加一个注解 LocalTCC这个表示开启 Seata 中的 TCC 模式。然后就是 TwoPhaseBusinessAction 注解两阶段提交的注解这个注解有三个属性第一个 name 就是处理两阶段提交的 bean 的名字其实就是当前 bean 的名字当前类名首字母小写。两阶段第一阶段就是 prepare 阶段也就是预处理阶段 。TwoPhaseBusinessAction 注解所在的方法第二阶段则分为两种情况提交或者回滚分别对应了两个不同的方法commitMethod 和 rollbackMethod 就指明了相应的方法。一阶段的 prepare 需要开发者手动调用二阶段的 commit 或者 rollback 则是系统自动调用。prepare 中的方法是由开发者来传递的而在二阶段的方法中相关的参数我们需要从 BusinessActionContext 中获取BusinessActionContextParameter 注解就是将对应的参数放入到 BusinessActionContext 中注意需要给每一个参数取一个名字将来可以从 BusinessActionContext 中取出对应的参数。另外需要注意接口的返回值设计成 boolean用以表示相应的操作执行成功还是失败返回 false 表示执行失败默认会有重试机制进行重试。 com/gm/seata/openfeign/service/impl/AccountServiceImpl.java package com.gm.seata.openfeign.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.gm.seata.openfeign.entity.Account; import com.gm.seata.openfeign.mapper.AccountMapper; import com.gm.seata.openfeign.service.AccountService; import com.gm.seata.openfeign.util.ErrorEnum; import io.seata.rm.tcc.api.BusinessActionContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;Slf4j Service public class AccountServiceImpl implements AccountService {AutowiredAccountMapper accountMapper;/*** 预扣款阶段检查账户余额** param userId* param money* return*/Transactional(rollbackFor Exception.class)public Account prepare(BusinessActionContext actionContext, String userId, BigDecimal money) {Account account accountMapper.getAccountByUserId(userId);if (account null) {//throw new RuntimeException(账户不存在);throw new RuntimeException(String.valueOf(ErrorEnum.NO_SUCH_ACCOUNT.getCode()));}// 账户余额 与 本次消费金额进行 比较if (account.getMoney().compareTo(money) 0) {//throw new RuntimeException(余额不足预扣款失败);throw new RuntimeException(String.valueOf(ErrorEnum.ACCOUNT_LOW_PREPARE.getCode()));}account.setFreezeMoney(account.getFreezeMoney().add(money));account.setMoney(account.getMoney().subtract(money));QueryWrapper query new QueryWrapper();query.eq(user_id, userId);Integer i accountMapper.update(account, query);log.info({} 账户预扣款 {} 元, userId, money);return account;}/*** 实际扣款阶段** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean commit(BusinessActionContext actionContext) {String userId (String) actionContext.getActionContext(userId);BigDecimal money new BigDecimal(actionContext.getActionContext(money).toString());Account account accountMapper.getAccountByUserId(userId);// 账户冻结金额 与 本次消费金额进行 比较if (account.getFreezeMoney().compareTo(money) 0) {// 抛出指定异常throw new RuntimeException(String.valueOf(ErrorEnum.ACCOUNT_LOW_COMMIT.getCode()));}account.setFreezeMoney(account.getFreezeMoney().subtract(money));QueryWrapper query new QueryWrapper();query.eq(user_id, userId);Integer i accountMapper.update(account, query);log.info({} 账户扣款 {} 元, userId, money);return i 1;}/*** 账户回滚阶段** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean rollback(BusinessActionContext actionContext) {String userId (String) actionContext.getActionContext(userId);BigDecimal money new BigDecimal(actionContext.getActionContext(money).toString());Account account accountMapper.getAccountByUserId(userId);if (account.getFreezeMoney().compareTo(money) 0) {account.setFreezeMoney(account.getFreezeMoney().subtract(money));account.setMoney(account.getMoney().add(money));QueryWrapper query new QueryWrapper();query.eq(user_id, userId);Integer i accountMapper.update(account, query);log.info({} 账户释放冻结金额 {} 元, userId, money);return i 1;}log.info({} 账户资金已释放, userId);// 说明prepare中抛出异常未冻结资金return true;}}5.3.3.4 Controller类 com/gm/seata/openfeign/controller/AccountController.java import com.gm.seata.openfeign.service.AccountService; import com.gm.seata.openfeign.util.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal;RestController public class AccountController {AutowiredAccountService accountService;/*** 扣除账户余额** param userId* param money* return*/RequestMapping(value deduct, method RequestMethod.GET)public RAccount deduct(RequestParam(userId) String userId, RequestParam(money) BigDecimal money) {return R.ok(accountService.prepare(null, userId, money));} }5.4 storage-tcc 搭建 5.4.1 完整依赖 减少重复内容请参考 5.3.1 部分自动修改 5.4.2 配置文件 减少重复内容请参考 5.3.2 部分自动修改 5.4.3 功能搭建 5.4.3.1 启动类 com/gm/seata/openfeign/StorageTCCApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;SpringBootApplication EnableDiscoveryClient public class StorageTCCApplication {public static void main(String[] args) {SpringApplication.run(StorageTCCApplication.class, args);} }5.4.3.2 Mapper类 com/gm/seata/openfeign/mapper/StorageMapper.java import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.gm.seata.openfeign.entity.Storage; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select;Mapper public interface StorageMapper extends BaseMapperStorage {Select(SELECT * FROM t_storage WHERE commodity_code #{commodityCode} limit 1)Storage getStorageByCommodityCode(Param(commodityCode) String commodityCode); }5.4.3.3 Service类 com/gm/seata/openfeign/service/StorageService.java import com.gm.seata.openfeign.entity.Storage; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction;LocalTCC public interface StorageService {/*** 执行资源检查及预业务操作*/// BusinessActionContextParameter 注解就是将对应的参数放入到 BusinessActionContext 中将来可以从 BusinessActionContext 中取出对应的参数。TwoPhaseBusinessAction(name storageService, commitMethod commit, rollbackMethod rollback)Storage prepare(BusinessActionContext actionContext, BusinessActionContextParameter(paramName commodityCode) String commodityCode, BusinessActionContextParameter(paramName count) Integer count);/*** 全局事物进行提交*/boolean commit(BusinessActionContext actionContext);/*** 全局事务进行回滚*/boolean rollback(BusinessActionContext actionContext); }减少重复内容请参考 5.3.3 部分说明 com/gm/seata/openfeign/service/impl/StorageServiceImpl.java import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.gm.seata.openfeign.entity.Storage; import com.gm.seata.openfeign.mapper.StorageMapper; import com.gm.seata.openfeign.service.StorageService; import com.gm.seata.openfeign.util.ErrorEnum; import io.seata.rm.tcc.api.BusinessActionContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;Slf4j Service public class StorageServiceImpl implements StorageService {AutowiredStorageMapper storageMapper;/*** 扣除商品库存预处理阶段进行商品库存冻结** param commodityCode* param count* return*/Transactional(rollbackFor Exception.class)public Storage prepare(BusinessActionContext actionContext, String commodityCode, Integer count) {Storage storage storageMapper.getStorageByCommodityCode(commodityCode);if (storage null) {//throw new RuntimeException(商品不存在);throw new RuntimeException(String.valueOf(ErrorEnum.NO_SUCH_COMMODITY.getCode()));}if (storage.getCount() count) {//throw new RuntimeException(库存不足预扣库存失败);throw new RuntimeException(String.valueOf(ErrorEnum.STORAGE_LOW_PREPARE.getCode()));}storage.setFreezeCount(storage.getFreezeCount() count);storage.setCount(storage.getCount() - count);QueryWrapper query new QueryWrapper();query.eq(commodity_code, commodityCode);Integer i storageMapper.update(storage, query);log.info({} 商品库存冻结 {} 个, commodityCode, count);return storage;}/*** 扣除商品库存提交阶段进行商品库存扣除** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean commit(BusinessActionContext actionContext) {String commodityCode (String) actionContext.getActionContext(commodityCode);Integer count (Integer) actionContext.getActionContext(count);Storage storage storageMapper.getStorageByCommodityCode(commodityCode);if (storage.getFreezeCount() count) {//throw new RuntimeException(库存不足扣库存失败);throw new RuntimeException(String.valueOf(ErrorEnum.STORAGE_LOW_COMMIT.getCode()));}storage.setFreezeCount(storage.getFreezeCount() - count);QueryWrapper query new QueryWrapper();query.eq(commodity_code, commodityCode);int i storageMapper.update(storage, query);log.info({} 商品库存扣除 {} 个, commodityCode, count);return i 1;}/*** 扣除商品库存回滚阶段** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean rollback(BusinessActionContext actionContext) {String commodityCode (String) actionContext.getActionContext(commodityCode);Integer count (Integer) actionContext.getActionContext(count);Storage storage storageMapper.getStorageByCommodityCode(commodityCode);if (storage.getFreezeCount() count) {storage.setFreezeCount(storage.getFreezeCount() - count);storage.setCount(storage.getCount() count);QueryWrapper query new QueryWrapper();query.eq(commodity_code, commodityCode);int i storageMapper.update(storage, query);log.info({} 商品释放库存 {} 个, commodityCode, count);return i 1;}// 说明 prepare 阶段就没有冻结return true;} }5.4.3.4 Controller类 com/gm/seata/openfeign/controller/StorageController.java import com.gm.seata.openfeign.entity.Storage; import com.gm.seata.openfeign.service.StorageService; import com.gm.seata.openfeign.util.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;RestController public class StorageController {AutowiredStorageService storageService;/*** 扣除商品库存** param commodityCode* param count* return*/RequestMapping(value deduct, method RequestMethod.GET)public RStorage deduct(RequestParam(commodityCode) String commodityCode, RequestParam(count) Integer count) {return R.ok(storageService.prepare(null, commodityCode, count));} }5.5 order-tcc 搭建 5.5.1 完整依赖 减少重复内容请参考 5.3.1 部分自动修改 5.5.2 配置文件 减少重复内容请参考 5.3.2 部分自动修改 5.5.3 功能搭建 5.5.3.1 启动类 com/gm/seata/openfeign/OrderTCCApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients;SpringBootApplication EnableDiscoveryClient EnableFeignClients(com.gm.seata.openfeign.feign) public class OrderTCCApplication {public static void main(String[] args) {SpringApplication.run(OrderTCCApplication.class, args);} }5.5.3.2 Mapper类 com/gm/seata/openfeign/mapper/OrderMapper.java import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.gm.seata.openfeign.entity.Order; import org.apache.ibatis.annotations.Mapper;Mapper public interface OrderMapper extends BaseMapperOrder {}5.5.3.3 Service类 com/gm/seata/openfeign/service/OrderService.java import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction;LocalTCC public interface OrderService {/*** 执行资源检查及预业务操作*/// BusinessActionContextParameter 注解就是将对应的参数放入到 BusinessActionContext 中将来可以从 BusinessActionContext 中取出对应的参数。TwoPhaseBusinessAction(name storageService, commitMethod commit, rollbackMethod rollback)boolean prepare(BusinessActionContext actionContext, BusinessActionContextParameter(paramName userId) String userId, BusinessActionContextParameter(paramName commodityCode) String commodityCode, BusinessActionContextParameter(paramName count) Integer count);/*** 全局事物进行提交*/boolean commit(BusinessActionContext actionContext);/*** 全局事务进行回滚*/boolean rollback(BusinessActionContext actionContext); } 减少重复内容请参考 5.3.3 部分说明 com/gm/seata/openfeign/service/impl/OrderServiceImpl.java package com.gm.seata.openfeign.service.impl;import com.gm.seata.openfeign.entity.Account; import com.gm.seata.openfeign.entity.Order; import com.gm.seata.openfeign.feign.AccountServiceApi; import com.gm.seata.openfeign.mapper.OrderMapper; import com.gm.seata.openfeign.service.OrderService; import com.gm.seata.openfeign.util.ErrorEnum; import com.gm.seata.openfeign.util.R; import io.seata.rm.tcc.api.BusinessActionContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;Slf4j Service public class OrderServiceImpl implements OrderService {AutowiredOrderMapper orderMapper;AutowiredAccountServiceApi accountServiceApi;/*** 创建商品订单预处理阶段扣除账户余额** param commodityCode* param count* return*/Transactional(rollbackFor Exception.class)public boolean prepare(BusinessActionContext actionContext, String userId, String commodityCode, Integer count) {//先去扣款假设每个产品100块钱RAccount accountResult null;try {accountResult accountServiceApi.deduct(userId, new BigDecimal(count * 100.0));} catch (Exception e) {e.printStackTrace();// 远程方法调用失败throw new RuntimeException(e.getMessage());}log.info({} 用户账户信息 {}, userId, accountResult.getData());log.info({} 用户购买的 {} 商品共计 {} 件预下单成功, userId, commodityCode, count);return true;}/*** 创建商品订单提交阶段创建订单记录** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean commit(BusinessActionContext actionContext) {String userId (String) actionContext.getActionContext(userId);String commodityCode (String) actionContext.getActionContext(commodityCode);Integer count (Integer) actionContext.getActionContext(count);Order order new Order();order.setCount(count);order.setCommodityCode(commodityCode);order.setUserId(userId);order.setMoney(new BigDecimal(count * 100.0));int i orderMapper.insert(order);log.info({} 用户购买的 {} 商品共计 {} 件下单成功, userId, commodityCode, count);return i 1;}/*** 创建商品订单回滚阶段暂无业务操作** param actionContext* return*/Transactional(rollbackFor Exception.class)public boolean rollback(BusinessActionContext actionContext) {String userId (String) actionContext.getActionContext(userId);String commodityCode (String) actionContext.getActionContext(commodityCode);Integer count (Integer) actionContext.getActionContext(count);log.info({} 用户购买的 {} 商品共计 {} 件订单回滚成功, userId, commodityCode, count);return true;}} 5.5.3.4 Controller类 com/gm/seata/openfeign/controller/OrderController.java import com.gm.seata.openfeign.service.OrderService; import com.gm.seata.openfeign.util.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;RestController public class OrderController {AutowiredOrderService orderService;/*** 创建订单** param userId* param commodityCode* param count* return*/RequestMapping(value createOrder, method RequestMethod.GET)public RBoolean createOrder(RequestParam(userId) String userId, RequestParam(commodityCode) String commodityCode, RequestParam(count) Integer count) {return R.ok(orderService.prepare(null, userId, commodityCode, count));} }5.6 business-tcc 搭建 5.6.1 完整依赖 减少重复内容请参考 5.3.1 部分自动修改 5.6.2 配置文件 减少重复内容请参考 5.3.2 部分自动修改 5.6.3 功能搭建 5.6.3.1 启动类 com/gm/seata/openfeign/BusinessTCCApplication.java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients;SpringBootApplication EnableDiscoveryClient EnableFeignClients(com.gm.seata.openfeign.feign) public class BusinessTCCApplication {public static void main(String[] args) {SpringApplication.run(BusinessTCCApplication.class, args);} } 5.6.3.2 Service类 com/gm/seata/openfeign/service/BusinessService.java public interface BusinessService {void buy(String userId, String commodityCode, Integer count); }com/gm/seata/openfeign/service/impl/BusinessServiceImpl.java import com.gm.seata.openfeign.feign.OrderServiceApi; import com.gm.seata.openfeign.feign.StorageServiceApi; import com.gm.seata.openfeign.service.BusinessService; import io.seata.core.context.RootContext; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;Slf4j Service public class BusinessServiceImpl implements BusinessService {AutowiredStorageServiceApi storageServiceApi;AutowiredOrderServiceApi orderServiceApi;/*** 下单购买先扣除库存再创建订单** param userId* param commodityCode* param count*/GlobalTransactionalpublic void buy(String userId, String commodityCode, Integer count) {String xid RootContext.getXID();log.info(xid{}, xid);/*** 扣除库存*/// 只有抛出异常才能触发GlobalTransactional 的回滚逻辑处理try {storageServiceApi.deduct(commodityCode, count);} catch (Exception e) {throw new RuntimeException(e.getMessage());}/*** 创建订单*/try {orderServiceApi.createOrder(userId, commodityCode, count);} catch (Exception e) {// 远程方法调用失败throw new RuntimeException(e.getMessage());}} }5.6.3.4 Controller类 com/gm/seata/openfeign/controller/OrderController.java import com.gm.seata.openfeign.service.BusinessService; import com.gm.seata.openfeign.util.ErrorEnum; import com.gm.seata.openfeign.util.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;RestController public class BusinessController {AutowiredBusinessService businessService;/*** 商品下单购买** param userId* param commodityCode* param count* return*/RequestMapping(value buy, method RequestMethod.GET)public RString buy(RequestParam(userId) String userId, RequestParam(commodityCode) String commodityCode, RequestParam(count) Integer count) {try {businessService.buy(userId, commodityCode, count);return R.ok(下单成功, );} catch (Exception e) {e.printStackTrace();int code Integer.parseInt(e.getMessage());return R.restResult(下单失败, code, ErrorEnum.getEnumByCode(code).getTitle());}} }六、示例说明 由第四章节可知账户余额500库存6每件商品单价100元。 请求地址http://127.0.0.1:4000/buy?userIduser1count2commodityCodeiphone 每请求一次扣除余额200元扣除库存2个已知可正常下单2次 第三次请求因余额不足进行全局事务回滚 以下为仓储服务回滚信息释放已冻结的2个库存
http://www.hkea.cn/news/14286797/

相关文章:

  • 做视频网站 许可网站开发 简历
  • 熟练掌握网站开发技术响应式中文网站模板
  • 衣联网和一起做网站 哪家强dede部署两个网站
  • 营销型网站设计官网深圳 网站开发
  • 涞源县住房和城乡建设局网站论坛网站如何备案
  • dedecms 网站 经常无法连接网站建站公司
  • 企业建设网站公司名称大全动态型网站建设
  • 文学网站建设电子商务网站建设ppt模板
  • 浪漫网站建设998元网站建设优化
  • 博物馆网站建设说明互联网推广品牌
  • 怎么制作自己的作品集搜索引擎排名优化技术
  • 企业建设网站的比例wordpress 下载页面模板怎么用
  • 网站销售好做吗公司网站的推广
  • 魏县网站建设东莞最新消息今天
  • 常用于做网站的软件专门做图表的网站
  • 做全国性的app网站推广多少怎么查开发商剩余房源
  • 山西省建设厅政务中心网站企业网站模板 演示
  • 网站设计的研究方法有哪些wordpress print_r
  • 免费网站模板的制作方法济宁哪里有网站建设
  • 网络规划设计师考试资料百度云帝国网站seo
  • 郑州网站关键词优化外包如何评估一个网站seo的优异程度
  • 工长网站开发wordpress主页乱码
  • 如何用dw做网站底页如何做网站商城
  • 做网站的学校有哪些网站建设中 显示 虚拟机
  • 建设银行etc的网站是哪个好网站运营成功案例
  • 给境外网站网站做代理vs2010 网站开发教程
  • 微商货源网站大全酒业为什么做网站
  • 苏州设计网站英文网站建设 江门
  • 网站优化的推广汕头seo优化流程
  • 阿里云手机网站建设多少钱重庆网站建设方案