springcloud和seata和nacos整合详细教程

一、环境准备
已经具有的环境:
mysql8.0.25
在这里插入图片描述

nacos
在这里插入图片描述
资源

资源名称地址说明
nacoshttps://github.com/alibaba/nacos/tags服务发现、注册中心、配置中心
seata1.4.2https://seata.io/zh-cn/blog/download.html分布式事务

Seata介绍

二、seata AT模式整合

1. nacos配置

1.1、将下载好的nacos解压到目标目录
在这里插入图片描述
1.2、持久化nacos到MySQL,本地新建nacos数据库,
找到nacos/conf下面的nacos-mysql.sql脚本执行
在这里插入图片描述
在这里插入图片描述
导入成功就会出现这些表
在这里插入图片描述

1.3、修改conf目录下的application.properties配置文件,如下图:
在这里插入图片描述
1.4、进入bin目录下新建start.bat文件,用记事本打开,输入

startup.cmd -m standalone

双击start.bat启动,访问http://localhost:8848/nacos/#/login 登录即可
在这里插入图片描述
在这里插入图片描述
进入到nacos控制台默认用户名密码都是nacos
点击命名空间,在点击新建命名空间
在这里插入图片描述
新建一个seata命名空间
在这里插入图片描述
在这里插入图片描述

2、Seata安装配置

2.1、seata下载地址:https://seata.io/zh-cn/blog/download.html
下载之后解压

在这里插入图片描述
2.2、修改conf下的配置文件file.conf
在这里插入图片描述

2.3、步骤2初始化化数据库需要的SQL地址: https://github.com/seata/seata/tree/develop/script/server/db
创建数据库seata,导入sql文件
导入成功之后
在这里插入图片描述

2.4、修改registry.conf的注册中心配置
在这里插入图片描述
2.5、把seata配置导入到nacos
nacos-config.sh脚本准备:
下载地址https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh,并将文件置于seata的conf目录下
2.6、config.txt准备,在conf的同级目录下新建config.txt文件

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
#修改my_test_tx_group为自定义服务seata-group
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=db
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.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
store.db.user=root
store.db.password=123456
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
client.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

完成后的目录样子
在这里插入图片描述
2.7、在conf目录下,打开git bash,输入 sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 -u nacos -w nacos,其中-t是nacos命名空间,可删除(当然建议给seata单独建命名空间,这样以后配置多了,可以很直观的找到),这个命名空间就是我们刚刚新建的seata的id,不能乱输入,不然nacos配置文件没有
在这里插入图片描述
在这里插入图片描述
若没有git则去下载安装即可,执行成功的样子
在这里插入图片描述
其中config.txt是配置文件,耳nacos-config.sh的作用是将配置文件的属性注入到nacos中,这样就不需要将file.conf和registry.conf放到我们的项目里,若没有找到config.txt则要把它放到seata的根目录配置就直接从nacos中读取。如图:
在这里插入图片描述

然后去nacos控制台
在这里插入图片描述
可以看到成功加载

3、项目整合seata

  • 数据库准备:
    三个数据:seata_accountseata_orderseata_storage
    sql语句:
CREATE DATABASE seata_order;
 
CREATE DATABASE seata_storage;
 
CREATE DATABASE seata_account;

建表语句注意区分数据库:
t_order:

CREATE TABLE t_order (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
  `count` INT(11) DEFAULT NULL COMMENT '数量',
  `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
  `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' 
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
 
SELECT * FROM t_order;

t_storage:

 
CREATE TABLE t_storage (
 `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
 `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
 `total` INT(11) DEFAULT NULL COMMENT '总库存',
 `used` INT(11) DEFAULT NULL COMMENT '已用库存',
 `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
 
INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');
SELECT * FROM t_storage;
 

t_account

 
CREATE TABLE t_account (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
  `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)  VALUES ('1', '1', '1000', '0', '1000');
SELECT * FROM t_account;

最后分别在每一个数据库下面创建undo_log表:
undo_logo:

 
 
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
DROP TABLE `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;
 


建成功之后:
在这里插入图片描述
在这里插入图片描述

  • 新建订单模块seata-order-service2001
    在这里插入图片描述
  • pom依赖
<dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!--   排除默认的-->
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--mysql-druid-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  • 查看依赖
    在这里插入图片描述
  • 修改application.yml配置文件
server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
#      seata:
#        #自定义事务组名称需要与seata-server中的对应
#        tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
#        namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 不指定命名空间。默认public
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

seata:
  tx-service-group: my_test_tx_group #这里要特别注意和nacos中配置的要保持一直
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      #namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 默认public
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21  #2.2中配置所在命名空间ID,入未配置 默认public空间
  service:
    vgroup-mapping:
      my_test_tx_group: default		# 这里要特别注意和nacos中配置的要保持一直


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以是public,不然就会出现

No available service

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 实体类OrderCommonResult
    order
package com.zhubayi.springcloud.alibaba.doamin;

import lombok.Data;

import java.math.BigDecimal;

/**
 * @author zhubayi
 */
@Data
public class Order {
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //订单状态:0:创建中;1:已完结
}

CommonResult

package com.zhubayi.springcloud.alibaba.doamin;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


/**
 * @author zhubayi
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
}

  • dao层创建OrderDao接口
package com.zhubayi.springcloud.alibaba.dao;

import com.zhubayi.springcloud.alibaba.doamin.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author zhubayi
 */
@Mapper
public interface OrderDao {
    /**
     * 创建订单
     * @param order
     */
    void createOrder(Order order);

    /**
     * 更新订单
     * @param userId
     * @param status
     */
    void updateStatus(@Param("userId") Long userId, @Param("status") Integer status);
}

  • serviceOrderServiceStorageServiceAccountService

OrderService

package com.zhubayi.springcloud.alibaba.service;

import com.zhubayi.springcloud.alibaba.doamin.Order;

/**
 * @author zhubayi
 */
public interface OrderService {
    /**
     * 创建订单
     * @param order
     */
    void createOrder(Order order);
}

StorageService

package com.zhubayi.springcloud.alibaba.service;

import com.zhubayi.springcloud.alibaba.doamin.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

/**
 * @author zhubayi
 */
@FeignClient(value = "seata-storage-service")
public interface StorageService
{
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

AccountService

package com.zhubayi.springcloud.alibaba.service;


import com.zhubayi.springcloud.alibaba.doamin.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;


@FeignClient(value = "seata-account-service")
public interface AccountService
{
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

创建orderService的实现类OrderServiceImpl

package com.zhubayi.springcloud.alibaba.service.impl;

import com.zhubayi.springcloud.alibaba.dao.OrderDao;
import com.zhubayi.springcloud.alibaba.doamin.Order;
import com.zhubayi.springcloud.alibaba.service.AccountService;
import com.zhubayi.springcloud.alibaba.service.OrderService;
import com.zhubayi.springcloud.alibaba.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author zhubayi
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;
    /**
     * 创建订单
     *
     * @param order
     */
    @Override
    public void createOrder(Order order) {
        log.info("=====>{}","开始新建订单");
        orderDao.createOrder(order);
        log.info("=====>{}","新建订单成功");
        log.info("=====>{}","库存减少");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("=====>{}","库存减少成功");
        log.info("=====>{}","开始减少余额");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("=====>{}","减少余额成功");

        log.info("=====>{}","开始修改订单状态");
        orderDao.updateStatus(order.getUserId(),0);
        log.info("=====>{}","修改订单状态成功");

        log.info("=====>{}","end");
    }
}

  • 创建OrderController
package com.zhubayi.springcloud.alibaba.controller;

import com.zhubayi.springcloud.alibaba.doamin.CommonResult;
import com.zhubayi.springcloud.alibaba.doamin.Order;
import com.zhubayi.springcloud.alibaba.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
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 zhubayi
 */
@RestController
@RequestMapping("order")
public class OrderController {
    @Autowired
    private OrderService orderService;
    @GetMapping("create")
    public CommonResult create(Order order){
        orderService.createOrder(order);
        return new CommonResult(200,"创建成功");
    }
}

在resources目录下创建mapper目录,再mapper下创建OrderDao.xml文件

<?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.zhubayi.springcloud.alibaba.dao.OrderDao">
    <resultMap id="BaseMap" type="com.zhubayi.springcloud.alibaba.doamin.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>
    <insert id="createOrder">
        insert  into t_order(id , user_id , product_id ,  count,  money ,  status  )
        values (null,#{userId},#{productId},#{count},#{money},0)
    </insert>
    <update id="updateStatus">
        update t_order set status=1 where user_id=#{userId} and status=#{status}
    </update>
</mapper>
  • 主启动类SeataOrderMainApp2001
package com.zhubayi.springcloud.alibaba;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


/**
 * @author zhubayi
 */
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan(basePackages = {"com.zhubayi.springcloud.alibaba.dao"})
@SpringBootApplication
public class SeataOrderMainApp2001
{

    public static void main(String[] args)
    {
        SpringApplication.run(SeataOrderMainApp2001.class, args);
    }
}

完成之后的目录样子:
在这里插入图片描述

  • 新建库存模块seata-storage-service2002
    在这里插入图片描述
  • pom文件依赖
 <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  • 修改配置文件application.yml,这个与前面的订单模块seata-order-service2001几乎一样,端口和名字不同,数据库换为seata_storage
server:
  port: 2002

spring:
  application:
    name: seata-storage-service
  cloud:
    alibaba:
#      seata:
#        tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

seata:
  tx-service-group: my_test_tx_group		#这里要特别注意和nacos中配置的要保持一直
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      #namespace: ${spring.cloud.nacos.discovery.namespace}   #2.2中配置所在命名空间ID,入未配置 默认public空间
  service:
    vgroup-mapping:
      my_test_tx_group: default		# 这里要特别注意和nacos中配置的要保持一直



  • 实体类Storage
    Storage:
package com.zhubayi.springcloud.alibaba.domain;

import lombok.Data;

@Data
public class Storage {

    private Long id;

    /**
     * 产品id
     */
    private Long productId;

    /**
     * 总库存
     */
    private Integer total;

    /**
     * 已用库存
     */
    private Integer used;

    /**
     * 剩余库存
     */
    private Integer residue;
}

CommonResult与订单模块一样

  • serviceStorageService
package com.zhubayi.springcloud.alibaba.service;


public interface StorageService {
    /**
     * 扣减库存
     */
    void decrease(Long productId, Integer count);
}

StorageService的实现类StorageServiceImpl

package com.zhubayi.springcloud.alibaba.service.impl;


import com.zhubayi.springcloud.alibaba.dao.StorageDao;
import com.zhubayi.springcloud.alibaba.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


@Service
public class StorageServiceImpl implements StorageService {

    private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);

    @Resource
    private StorageDao storageDao;

    /**
     * 扣减库存
     */
    @Override
    public void decrease(Long productId, Integer count) {
        LOGGER.info("------->storage-service中扣减库存开始");
        storageDao.decrease(productId,count);
        LOGGER.info("------->storage-service中扣减库存结束");
    }
}

  • StorageDao接口:
package com.zhubayi.springcloud.alibaba.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface StorageDao {

    /**
     *扣减库存
     **/
    void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}

StorageDao.xml文件

<?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.zhubayi.springcloud.alibaba.dao.StorageDao">

    <resultMap id="BaseResultMap" type="com.zhubayi.springcloud.alibaba.domain.Storage">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="total" property="total" jdbcType="INTEGER"/>
        <result column="used" property="used" jdbcType="INTEGER"/>
        <result column="residue" property="residue" jdbcType="INTEGER"/>
    </resultMap>

    <update id="decrease">
        UPDATE
            t_storage
        SET
            used = used + #{count},residue = residue - #{count}
        WHERE
            product_id = #{productId}
    </update>

</mapper>
  • StorageController
package com.zhubayi.springcloud.alibaba.controller;


import com.zhubayi.springcloud.alibaba.domain.CommonResult ;
import com.zhubayi.springcloud.alibaba.service.StorageService ;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StorageController {

    @Autowired
    private StorageService storageService;

    /**
     * 扣减库存
     */
    @RequestMapping("/storage/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId, count);
        return new CommonResult(200,"扣减库存成功!");
    }
}

  • 主启动类SeataStorageServiceApplication2002
package com.zhubayi.springcloud.alibaba;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinWorkerThread;

/**
 * @auther zzyy
 * @create 2019-12-12 17:31
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan(basePackages = {"com.zhubayi.springcloud.alibaba.dao"})
public class SeataStorageServiceApplication2002
{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataStorageServiceApplication2002.class, args);
    }
}

完成之后的样子:
在这里插入图片描述

  • 创建seata-account-service2003用户余额模块
    在这里插入图片描述
  • pom文件依赖
 <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  • 新建配置文件application.yml
server:
  port: 2003

spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      #seata:
        #tx-service-group: my_test_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        group: SEATA_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

seata:
  tx-service-group: my_test_tx_group		#这里要特别注意和nacos中配置的要保持一直
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      #namespace: ${spring.cloud.nacos.discovery.namespace}   #2.2中配置所在命名空间ID,入未配置 默认public空间
  service:
    vgroup-mapping:
      my_test_tx_group: default		# 这里要特别注意和nacos中配置的要保持一直



注意数据库名称

  • 实体类Account
package com.zhubayi.springcloud.alibaba.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 总额度
     */
    private BigDecimal total;

    /**
     * 已用额度
     */
    private BigDecimal used;

    /**
     * 剩余额度
     */
    private BigDecimal residue;
}

CommonResult与前面的相同

  • AccountDao接口
package com.zhubayi.springcloud.alibaba.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;

/**
 * @author zhubayi
 */
@Mapper
public interface AccountDao {

    /**
     * 扣减账户余额
     * @param userId
     * @param money
     */
    void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
}

  • AccountService接口
package com.zhubayi.springcloud.alibaba.service;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.math.BigDecimal;


public interface AccountService {

    /**
     * 扣减账户余额
     * @param userId 用户id
     * @param money 金额
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

  • AccountService接口的实现类AccountServiceImpl
package com.zhubayi.springcloud.alibaba.service.impl;


import com.zhubayi.springcloud.alibaba.dao.AccountDao;
import com.zhubayi.springcloud.alibaba.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * 账户业务实现类
 */
@Service
public class AccountServiceImpl implements AccountService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);


    @Resource
    AccountDao accountDao;

    /**
     * 扣减账户余额
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
        LOGGER.info("------->account-service中扣减账户余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
       // try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
}

AccountController

package com.zhubayi.springcloud.alibaba.controller;

import com.zhubayi.springcloud.alibaba.domain.CommonResult;
import com.zhubayi.springcloud.alibaba.service.AccountService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;

@RestController
public class AccountController {

    @Resource
    AccountService accountService;

    /**
     * 扣减账户余额
     */
    @RequestMapping("/account/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
        accountService.decrease(userId,money);
        return new CommonResult(200,"扣减账户余额成功!");
    }
}

  • 启动类SeataAccountMainApp2003
package com.zhubayi.springcloud.alibaba;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


/**
 * @author zhubayi
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan({"com.zhubayi.springcloud.alibaba.dao"})
public class SeataAccountMainApp2003
{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataAccountMainApp2003.class, args);
    }
}

AccountMapper.xml文件

<?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.zhubayi.springcloud.alibaba.dao.AccountDao">

    <resultMap id="BaseResultMap" type="com.zhubayi.springcloud.alibaba.domain.Account">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="total" property="total" jdbcType="DECIMAL"/>
        <result column="used" property="used" jdbcType="DECIMAL"/>
        <result column="residue" property="residue" jdbcType="DECIMAL"/>
    </resultMap>

    <update id="decrease">
        UPDATE t_account
        SET
          residue = residue - #{money},used = used + #{money}
        WHERE
          user_id = #{userId};
    </update>

</mapper>

启动三个模块还有nacosseata
在这里插入图片描述

021-07-05 11:48:22.383  INFO 12568 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-07-05 11:48:22.469  INFO 12568 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'Nacos-Watch-Task-Scheduler'
2021-07-05 11:48:22.820  INFO 12568 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-07-05 11:48:22.895  INFO 12568 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 2001 (http) with context path ''
2021-07-05 11:48:22.899  INFO 12568 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, SEATA_GROUP seata-order-service 192.168.0.123:2001 register finished
2021-07-05 11:48:23.006  INFO 12568 --- [           main] c.z.s.alibaba.SeataOrderMainApp2001      : Started SeataOrderMainApp2001 in 4.239 seconds (JVM running for 4.756)
2021-07-05 11:48:23.395  INFO 12568 --- [)-192.168.0.123] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-07-05 11:48:23.395  INFO 12568 --- [)-192.168.0.123] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-07-05 11:48:23.400  INFO 12568 --- [)-192.168.0.123] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
`2021-07-05 11:49:20.607  INFO 12568 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.0.123:8091
2021-07-05 11:49:20.608  INFO 12568 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:TMROLE,address:192.168.0.123:8091,msg:< RegisterTMRequest{applicationId='seata-order-service', transactionServiceGroup='my_test_tx_group'} >
2021-07-05 11:49:20.613  INFO 12568 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient    : register TM success. client version:1.4.2, server version:1.4.2,channel:[id: 0xd7cc02ac, L:/192.168.0.123:54703 - R:/192.168.0.123:8091]
2021-07-05 11:49:20.613  INFO 12568 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 3 ms, version:1.4.2,role:TMROLE,channel:[id: 0xd7cc02ac, L:/192.168.0.123:54703 - R:/192.168.0.123:8091]`

注意出现黄色部分才是配置正确了,注册进了nacos

打开浏览器输入:http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
在这里插入图片描述
可以看到订单创建成功,再去看看数据库:
在这里插入图片描述
这时我们手动制造一个超时异常,默认openfeign远程调用1秒,没有返回数据就报错
在这里插入图片描述
再去访问:http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
在这里插入图片描述
再看看控制台:
订单模块报了超时异常,数据库里面多了一个订单,但是订单的状态没有改变,这里就要用分布式事务来处理。
在这里插入图片描述

为了解决这个问题,我们要在发生分布式事务的方法上加上@GlobalTransactiona注解
在这里插入图片描述

然后再去浏览去访问:http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
在这里插入图片描述

再去看看控制台:
在这里插入图片描述
也是超时异常

去查看数据库
在这里插入图片描述
order表并没有增加,accoutstorage表也没有发送改变。说明解决了分布式事务的问题.
为了看的更加详细我们打个断点看看
在这里插入图片描述
再去调用http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
在这里插入图片描述
在这里插入图片描述

  • 多了一条数据

库存扣减成功
在这里插入图片描述

查看全局锁、分支锁、表数据锁
查看seata库表锁状态信息
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 查看order库、storage库 undo_log回滚日志

在这里插入图片描述

  • 放开断点,让代码逻辑执行完成,查看回滚情况。
    代码执行结果:账户余额扣减超时,事务失败。
    在这里插入图片描述
    在这里插入图片描述
    回滚完成之后 undo_log,锁信息全部清除
    在这里插入图片描述
    至此,验证成功完成
    参考博客:
    博客1
    博客2
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值