# springcloud_seata_demo **Repository Path**: hwm0717/springcloud_seata_demo ## Basic Information - **Project Name**: springcloud_seata_demo - **Description**: seata 分布式事物框架demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-23 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README `seata`如果不是很了解的话可以直接到官网查看官方文档说明:[http://seata.io/zh-cn/docs/overview/what-is-seata.html](http://seata.io/zh-cn/docs/overview/what-is-seata.html) 如果你还对`SpringBoot`、`Dubbo`、`Nacos`、`Seata`、` Mybatis` 不是很了解的话,这里我为大家整理个它们的官网网站,如下 - SpringBoot:[https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) - Nacos:[https://nacos.io/zh-cn/docs/quick-start.html](https://nacos.io/zh-cn/docs/quick-start.html) - Seata:[https://seata.io/zh-cn/](https://seata.io/zh-cn/) - MyBatis:[http://www.mybatis.org/mybatis-3/zh/index.html](http://www.mybatis.org/mybatis-3/zh/index.html) **本例子主要模块为以下版本:** springboot版本:2.2.5.RELEASE spring-cloud版本:Hoxton.SR3 spring-cloud-alibaba版本:2.2.1.RELEASE seata版本:1.4.0 nacos版本:1.4.0 如果还没安装好nacos环境的可以参考文档:[nacos下载与安装](https://blog.csdn.net/u012946310/article/details/110043529) **整个业务逻辑由4个微服务提供支持:** - 库存服务(storage):扣除给定商品的存储数量。 - 订单服务(order):根据购买请求创建订单。 - 帐户服务(account):借记用户帐户的余额。 - 业务服务(business):处理业务逻辑。 - 公共模块(common):公共模块。 ## 一、下载并安装seata-server **1,下载seata** [https://github.com/seata/seata/releases](https://github.com/seata/seata/releases) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020112411144159.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) **2,解压并移动到 /usr/local/ 目录下** ```bash tar -zxvf seata-server-1.4.0.tar.gz mv seata seata-1.4.0 mv seata-1.4.0/ /usr/local/ cd /usr/local/seata-1.4.0/ ``` **3,修改 conf/registry.conf 配置** 目前seata支持如下的file、nacos 、apollo、zk、consul的注册中心和配置中心。这里我们以`nacos` 为例。 将 type 改为 nacos ```bash registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "192.168.2.112:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = "" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "192.168.2.112:8848" namespace = "" group = "SEATA_GROUP" username = "" password = "" } } ``` - serverAddr = "192.168.2.112:8848" :nacos 的地址 - namespace = "" :nacos的命名空间默认为`` - cluster = "default" :集群设置未默认 `default` **4,修改 config.txt 文件** config.txt 配置文件需要自行下载,下载后放到seata根目录下面,下载地址:[https://github.com/seata/seata/blob/develop/script/config-center/config.txt](https://github.com/seata/seata/blob/develop/script/config-center/config.txt) ```bash transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=file store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true store.db.user=username store.db.password=password store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 store.redis.host=127.0.0.1 store.redis.port=6379 store.redis.maxConn=10 store.redis.minConn=1 store.redis.database=0 store.redis.password=null store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898 ``` 配置的详细说明参考官网:[http://seata.io/zh-cn/docs/user/configurations.html](http://seata.io/zh-cn/docs/user/configurations.html) 这里主要修改了如下几项: - store.mode :存储模式 默认file 这里我修改为db 模式 ,并且需要三个表`global_table`、`branch_table`和`lock_table` - store.db.driverClassName=com.mysql.jdbc.Driver - store.db.db-type=mysql : 存储数据库的类型为`mysql` - store.db.url=jdbc:mysql://192.168.2.112:3306/seata_config?useUnicode=true : 修改为自己的数据库`url`、`port`、`数据库名称` - store.db.user=root :数据库的账号 - store.db.password=123456 :数据库的密码 - service.vgroupMapping.order-seata-tx-group=default - service.vgroupMapping.account-seata-tx-group=default - service.vgroupMapping.storage-seata-tx-group=default - service.vgroupMapping.business-seata-tx-group=default `注意:如果mysql版本为8.0以上的版本,需要使用8.x以上的驱动包,并且将 store.db.driverClassName=com.mysql.jdbc.Driver 配置改为 store.db.driverClassName=com.mysql.cj.jdbc.Driver` `然后直接到seata/lib目录下,将8版本的jar包粘贴到目录下` **db模式下的所需的三个表的数据库脚本下载地址:[https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql](https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql)** **5、将 Seata 配置添加到 Nacos 中** 脚本下载地址:[https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh](https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh) 下载后将脚本放到 seata bin/ 目录下,并将脚本权限改成具有可执行权限 ```bash cd bin chmod 755 nacos-config.sh ``` 执行 nacos-config.sh 脚本,将配置上传到nacos ```bash sh ./nacos-config.sh ``` 看到输出如下内容,说明脚本已经执行成功 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124120049233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) 登陆到nacos控制台也能够看到上传的配置内容 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124120127473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) **6、使用db模式启动 seata** ```bash sh seata-server.sh -h 192.168.2.112 -p 8091 -m db ``` ```bash Options: --host, -h The host to bind,主机绑定ip,nacos的注册地址 Default: 0.0.0.0 --port, -p The port to listen. Default: 8091 --storeMode, -m log store mode : file、db Default: file --help ``` Server端存储模式(store.mode)现有file、db、redis三种,file模式无需改动,直接启动即可,下面专门讲下db和redis启动步骤。 注: file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高; db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些; redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置. ## 二、代码构建 `项目地址:`[https://gitee.com/hwm0717/springcloud_seata_demo](https://gitee.com/hwm0717/springcloud_seata_demo) **4个微服务提供支持:** - 库存服务(storage):扣除给定商品的存储数量。 - 订单服务(order):根据购买请求创建订单。 - 帐户服务(account):借记用户帐户的余额。 - 业务服务(business):处理业务逻辑。 - 公共模块(common):公共模块。 `我们只需要在业务处理的方法handleBusiness添加一个注解 @GlobalTransactional 即可实现分布式事物的处理` 请求逻辑架构: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124174603786.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) `pom.xml文件` ```bash 4.0.0 pom org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE seata.demo springcloud_seata_demo 1.0-SNAPSHOT 1.8 2.2.5.RELEASE 8.0.21 1.1.22 1.4.0 Hoxton.SR3 2.2.1.RELEASE business account order storage common org.projectlombok lombok provided com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-openfeign com.baomidou mybatis-plus-boot-starter 3.3.0 com.github.pagehelper pagehelper-spring-boot-starter 1.2.12 mysql mysql-connector-java ${mysql.connector.java.version} com.alibaba druid-spring-boot-starter ${druid-spring-boot-starter.version} log4j log4j 1.2.17 com.spring4all swagger-spring-boot-starter 1.7.0.RELEASE com.alibaba.cloud spring-cloud-alibaba-seata 2.2.0.RELEASE io.seata seata-spring-boot-starter io.seata seata-spring-boot-starter ${seata.version} org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-dependencies ${springboot.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import ``` **1,库存服务:** ```bash @Data public class TStorage { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private String commodityCode; private String name; private Integer count; } ``` ```bash public interface TStorageMapper extends BaseMapper { /** * 扣减商品库存 * @Param: commodityCode 商品code count扣减数量 * @Return: */ int decreaseStorage(@Param("commodityCode") String commodityCode, @Param("count") Integer count); } ``` ```bash @RestController @RequestMapping("/storage") @Slf4j public class StorageServiceImpl extends ServiceImpl { @PostMapping("/decreaseStorage") @GlobalTransactional @Transactional public ObjectResponse decreaseStorage(@RequestBody CommodityDTO commodityDTO) { log.info("seata全局xid={}", RootContext.getXID()); int storage = baseMapper.decreaseStorage(commodityDTO.getCommodityCode(), commodityDTO.getCount()); ObjectResponse response = new ObjectResponse<>(); if (storage > 0) { response.setStatus(RspStatusEnum.SUCCESS.getCode()); response.setMessage(RspStatusEnum.SUCCESS.getMessage()); return response; } response.setStatus(RspStatusEnum.FAIL.getCode()); response.setMessage(RspStatusEnum.FAIL.getMessage()); return response; } } ``` ```bash @SpringBootApplication @EnableFeignClients @MapperScan("seata.demo.storage.mappers") public class StorageApplication { public static void main(String[] args) { SpringApplication.run(StorageApplication.class, args); } } ``` ```bash update t_storage set count = count-${count} where commodity_code = #{commodityCode} ``` **2,订单服务** ```bash @Data public class TOrder { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private String orderNo; private String userId; private String commodityCode; private Integer count; private Double amount; } ``` ```bash public interface TOrderMapper extends BaseMapper { /** * 创建订单 * @Param: order 订单信息 * @Return: */ void createOrder(@Param("order") TOrder order); } ``` ```bash @RestController @RequestMapping("/order") @Slf4j public class OrderServiceImpl extends ServiceImpl { @Autowired private AccountServiceFeign accountServiceFeign; @PostMapping("/createOrder") @GlobalTransactional @Transactional public ObjectResponse createOrder(@RequestBody OrderDTO orderDTO) { log.info("seata全局xid={}", RootContext.getXID()); ObjectResponse response = new ObjectResponse<>(); //扣减用户账户 AccountDTO accountDTO = new AccountDTO(); accountDTO.setUserId(orderDTO.getUserId()); accountDTO.setAmount(orderDTO.getOrderAmount()); ObjectResponse objectResponse = accountServiceFeign.decreaseAccount(accountDTO); //生成订单号 orderDTO.setOrderNo(UUID.randomUUID().toString().replace("-","")); //生成订单 TOrder tOrder = new TOrder(); BeanUtils.copyProperties(orderDTO,tOrder); tOrder.setCount(orderDTO.getOrderCount()); tOrder.setAmount(orderDTO.getOrderAmount().doubleValue()); try { baseMapper.createOrder(tOrder); } catch (Exception e) { e.printStackTrace(); response.setStatus(RspStatusEnum.FAIL.getCode()); response.setMessage(RspStatusEnum.FAIL.getMessage()); return response; } if (objectResponse.getStatus() != 200) { response.setStatus(RspStatusEnum.FAIL.getCode()); response.setMessage(RspStatusEnum.FAIL.getMessage()); return response; } response.setStatus(RspStatusEnum.SUCCESS.getCode()); response.setMessage(RspStatusEnum.SUCCESS.getMessage()); return response; } } ``` ```bash @SpringBootApplication @EnableFeignClients @MapperScan("seata.demo.order.mappers") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } ``` ```bash insert into t_order values(null,#{order.orderNo},#{order.userId},#{order.commodityCode},${order.count},${order.amount}) ``` **3,账户服务** ```bash @Data public class TAccount { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private String userId; private Double amount; } ``` ```bash public interface TAccountMapper extends BaseMapper { /** * 减少账户余额 * @param userId * @param amount * @return */ int decreaseAccount(@Param("userId") String userId, @Param("amount") Double amount); } ``` ```bash @RestController @RequestMapping("/account") @Slf4j public class AccountServiceImpl extends ServiceImpl { @PostMapping("/decreaseAccount") @GlobalTransactional @Transactional public ObjectResponse decreaseAccount(@RequestBody AccountDTO accountDTO) { log.info("seata全局xid={}", RootContext.getXID()); int account = baseMapper.decreaseAccount(accountDTO.getUserId(), accountDTO.getAmount().doubleValue()); ObjectResponse response = new ObjectResponse<>(); if (account > 0) { response.setStatus(RspStatusEnum.SUCCESS.getCode()); response.setMessage(RspStatusEnum.SUCCESS.getMessage()); return response; } response.setStatus(RspStatusEnum.FAIL.getCode()); response.setMessage(RspStatusEnum.FAIL.getMessage()); return response; } } ``` ```bash @SpringBootApplication @EnableFeignClients @MapperScan("seata.demo.account.mappers") public class AccountApplication { public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); } } ``` ```bash update t_account set amount = amount-${amount} where user_id = #{userId} ``` **4,业务服务** ```bash @RestController @RequestMapping("/business") @Slf4j public class BusinessServiceImpl { @Autowired private StorageServiceFeign storageServiceFeign; @Autowired private OrderServiceFeign orderServiceFeign; /** * 处理业务逻辑 正常的业务逻辑 * * @Param: * @Return: */ @GlobalTransactional @PostMapping("/handleBusiness") @ApiOperation("处理业务逻辑 正常的业务逻辑") public ObjectResponse handleBusiness(@RequestBody BusinessDTO businessDTO) { log.info("开始全局事务,XID = " + RootContext.getXID()); ObjectResponse objectResponse = new ObjectResponse<>(); //1、扣减库存 CommodityDTO commodityDTO = new CommodityDTO(); commodityDTO.setCommodityCode(businessDTO.getCommodityCode()); commodityDTO.setCount(businessDTO.getCount()); ObjectResponse storageResponse = storageServiceFeign.decreaseStorage(commodityDTO); //2、创建订单 OrderDTO orderDTO = new OrderDTO(); orderDTO.setUserId(businessDTO.getUserId()); orderDTO.setCommodityCode(businessDTO.getCommodityCode()); orderDTO.setOrderCount(businessDTO.getCount()); orderDTO.setOrderAmount(businessDTO.getAmount()); ObjectResponse response = orderServiceFeign.createOrder(orderDTO); if (storageResponse.getStatus() != 200 || response.getStatus() != 200) { throw new DefaultException(RspStatusEnum.FAIL); } objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode()); objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage()); objectResponse.setData(response.getData()); return objectResponse; } /** * 出处理业务服务,出现异常回顾 * * @param businessDTO * @return */ @GlobalTransactional @PostMapping("/handleBusiness2") @ApiOperation("出处理业务服务,出现异常回顾") @Transactional public ObjectResponse handleBusiness2(@RequestBody BusinessDTO businessDTO) { log.info("开始全局事务,XID = " + RootContext.getXID()); ObjectResponse objectResponse = new ObjectResponse<>(); //1、扣减库存 CommodityDTO commodityDTO = new CommodityDTO(); commodityDTO.setCommodityCode(businessDTO.getCommodityCode()); commodityDTO.setCount(businessDTO.getCount()); ObjectResponse storageResponse = storageServiceFeign.decreaseStorage(commodityDTO); //2、创建订单 OrderDTO orderDTO = new OrderDTO(); orderDTO.setUserId(businessDTO.getUserId()); orderDTO.setCommodityCode(businessDTO.getCommodityCode()); orderDTO.setOrderCount(businessDTO.getCount()); orderDTO.setOrderAmount(businessDTO.getAmount()); ObjectResponse response = orderServiceFeign.createOrder(orderDTO); // 打开注释测试事务发生异常后,全局回滚功能 if (true) { throw new RuntimeException("测试抛异常后,分布式事务回滚!"); } if (storageResponse.getStatus() != 200 || response.getStatus() != 200) { throw new DefaultException(RspStatusEnum.FAIL); } objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode()); objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage()); objectResponse.setData(response.getData()); return objectResponse; } } ``` ```bash @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableSwagger2Doc @EnableFeignClients public class BusinessApplication { public static void main(String[] args) { SpringApplication.run(BusinessApplication.class, args); } } ``` **5,公共模块** ```bash @Data public class AccountDTO implements Serializable { private Integer id; private String userId; private BigDecimal amount; } ``` ```bash @Data public class BusinessDTO implements Serializable { private String userId; private String commodityCode; private String name; private Integer count; private BigDecimal amount; } ``` ```bash @Data public class CommodityDTO implements Serializable { private Integer id; private String commodityCode; private String name; private Integer count; } ``` ```bash @Data public class OrderDTO implements Serializable { private String orderNo; private String userId; private String commodityCode; private Integer orderCount; private BigDecimal orderAmount; } ``` ```bash public enum RspStatusEnum { /** * SUCCESS */ SUCCESS(200,"成功"), /** * Fail rsp status enum. */ FAIL(999,"失败"), /** * Exception rsp status enum. */ EXCEPTION(500,"系统异常"); private int code; private String message; RspStatusEnum(int code, String message) { this.code = code; this.message = message; } /** * Gets code. * * @return the code */ public int getCode() { return code; } /** * Gets message. * * @return the message */ public String getMessage() { return message; } } ``` ```bash public class DefaultException extends RuntimeException{ private RspStatusEnum rspStatusEnum; public DefaultException(String message, Throwable cause) { super(message, cause); } public DefaultException(RspStatusEnum rspStatusEnum) { super(rspStatusEnum.getMessage()); this.rspStatusEnum = rspStatusEnum; } public DefaultException(RspStatusEnum rspStatusEnum, Throwable cause) { super(rspStatusEnum.getMessage(), cause); this.rspStatusEnum = rspStatusEnum; } public RspStatusEnum getRspStatusEnum() { return rspStatusEnum; } public void setRspStatusEnum(RspStatusEnum rspStatusEnum) { this.rspStatusEnum = rspStatusEnum; } } ``` ```bash @FeignClient(value = "account", path = "/account") public interface AccountServiceFeign { /** * 从账户扣钱 */ @ApiOperation("从账户扣钱") @PostMapping("/decreaseAccount") ObjectResponse decreaseAccount(@RequestBody AccountDTO accountDTO); } ``` ```bash @FeignClient(value = "order", path = "/order") public interface OrderServiceFeign { /** * 创建订单 */ @ApiOperation("创建订单") @PostMapping("/createOrder") ObjectResponse createOrder(@RequestBody OrderDTO orderDTO); } ``` ```bash @FeignClient(value = "storage", path = "/storage") public interface StorageServiceFeign { /** * 扣减库存 */ @ApiOperation("扣减库存") @PostMapping("/decreaseStorage") ObjectResponse decreaseStorage(@RequestBody CommodityDTO commodityDTO); } ``` ```bash @Data public class BaseResponse implements Serializable { private int status = 200; private String message; } ``` ```bash @Data public class ObjectResponse extends BaseResponse implements Serializable { private T data; } ``` **6,配置文件** 主要三个配置文件,每个应用里面复制一份,内容基本一致,只需要根据自己配置改下扫描的包和应用名称即可 `application-global.properties` ```bash #服务端口号 server.port=8080 spring.profiles.include=global,seata #服务名称 spring.application.name=business #nacos spring.cloud.nacos.discovery.server-addr=192.168.2.112:8848 #日志配置 logging.level.root=info logging.file.path=C:\\www\\logs\\${spring.application.name} #数据库连接信息 spring.datasource.url=jdbc:mysql://192.168.2.112:3306/springcloud_seata_demo?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=123456 #swagger swagger.enabled=true swagger.title=业务逻辑处理 swagger.description=业务逻辑处理 swagger.version=1.0.0 swagger.base-package=seata.demo.business.service #设置feign超时时间,解决第一次调用时超时问题 feign.client.config.default.connectTimeout=5000 feign.client.config.default.readTimeout=5000 ``` `application-seata.yml` ```bash #====================================Seata Config=============================================== seata: enabled: true application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-seata-tx-group # 事务群组(可以每个应用独立取名,也可以使用相同的名字) registry: file: name: file.conf type: nacos nacos: server-addr: 192.168.2.112:8848 namespace: cluster: default config: file: name: file.conf type: nacos nacos: namespace: server-addr: 192.168.2.112:8848 ``` `application.properties` ```bash #服务端口号 server.port=8080 #引入外部配置文件 spring.profiles.include=global,seata #服务名称 spring.application.name=business #nacos spring.cloud.nacos.discovery.server-addr=192.168.2.112:8848 #日志配置 logging.level.root=info logging.file.path=C:\\www\\logs\\${spring.application.name} #数据库连接信息 spring.datasource.url=jdbc:mysql://192.168.2.112:3306/springcloud_seata_demo?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=123456 #swagger swagger.enabled=true swagger.title=业务逻辑处理 swagger.description=业务逻辑处理 swagger.version=1.0.0 swagger.base-package=seata.demo.business.service #设置feign超时时间,解决第一次调用时超时问题 feign.client.config.default.connectTimeout=5000 feign.client.config.default.readTimeout=5000 ``` **7,准备数据库** `注意: MySQL必须使用InnoDB engine.` 创建数据库 并导入数据库脚本 这里为了简化我将这个三张表创建到一个库中,使用是三个数据源来实现。 ```sql DROP TABLE IF EXISTS `t_account`; CREATE TABLE `t_account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `amount` double(14,2) DEFAULT '0.00', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_account -- ---------------------------- INSERT INTO `t_account` VALUES ('1', '1', '4000.00'); -- ---------------------------- -- Table structure for t_order -- ---------------------------- DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(255) DEFAULT NULL, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT '0', `amount` double(14,2) DEFAULT '0.00', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_order -- ---------------------------- -- ---------------------------- -- Table structure for t_storage -- ---------------------------- DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `id` int(11) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `commodity_code` (`commodity_code`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_storage -- ---------------------------- INSERT INTO `t_storage` VALUES ('1', 'C201901140001', '水杯', '1000'); -- ---------------------------- -- Table structure for undo_log -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log -- ---------------------------- DROP TABLE IF EXISTS `undo_log`; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of undo_log -- ---------------------------- SET FOREIGN_KEY_CHECKS=1; ``` ## 三、测试 使用swagger发送一个下单请求,调用 /business/handleBusiness 方法:[http://localhost:8080/swagger-ui.html#/business-service-impl/handleBusinessUsingPOST](http://localhost:8080/swagger-ui.html#/business-service-impl/handleBusinessUsingPOST) 参数内容: ```bash { "amount": 100, "commodityCode": "C201901140001", "count": 100, "name": "水杯", "userId": "1" } ``` 返回结果: ```bash { "status": 200, "message": "成功", "data": null } ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182255542.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) 控制台打印 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182642277.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124181903584.png#pic_center) 事物提交成功,查看数据变化 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182027953.png#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182035883.png#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182043722.png#pic_center) 数据没有问题,全部按照正常流程提交了 **2,测试数据回滚:** 参数一样,通过swagger调用 /business/handleBusiness2 异常回滚方法 返回结果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182501586.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) 控制台输出 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182526806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201124182701516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5NDYzMTA=,size_16,color_FFFFFF,t_70#pic_center) 再次查看数据库数据,已经回滚,和上面的数据一致。