Spring cloud and seata and nacos integration detailed tutorial

Posted by hnissani on Sun, 23 Jan 2022 12:29:10 +0100

1, Environmental preparation
Existing environment:
mysql8.0.25

nacos

resources

Resource nameaddressexplain
nacoshttps://github.com/alibaba/nacos/tagsService discovery, registry, configuration center
seata1.4.2https://seata.io/zh-cn/blog/download.htmlDistributed transaction

Introduction to Seata

2, seata AT mode integration

1. nacos configuration

1.1. Unzip the downloaded nacos to the target directory

1.2. Persist nacos to MySQL and create a new nacos database locally,
Find the Nacos mysql.exe under nacos/conf SQL script execution


These tables will appear when the import is successful

1.3. Modify the application. In the conf directory Properties configuration file, as shown in the following figure:

1.4. Enter the bin directory to create a new start Bat file, open with Notepad, enter

startup.cmd -m standalone

Double click start Bat startup, access http://localhost:8848/nacos/#/login Just log in


Enter the nacos console. The default user name and password are all nacos
Click namespace, and then click new namespace

Create a new seata namespace

2. Seata installation configuration

2.1. Seata download address: https://seata.io/zh-cn/blog/download.html
Unzip after download


2.2. Modify the configuration file file under conf conf

2.3. Step 2: the SQL address required to initialize the database: https://github.com/seata/seata/tree/develop/script/server/db
Create database seata and import sql files
After successful import

2.4. Modify registry Registry configuration for conf

2.5. Import seata configuration into nacos
nacos-config.sh script preparation:
Download address https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh And put the file in the conf directory of Seata
2.6,config.txt, create a new config.txt in the same level directory of conf Txt file

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
#Modify my_test_tx_group is a custom service, 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

The finished directory looks like

2.7. In the conf directory, open git bash and enter sh nacos config sh -h localhost -p 8848 -g seata_ Group - t c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 - u nacos - w nacos, where - t is the nacos namespace, which can be deleted (of course, it is recommended to create a separate namespace for seata, so that it can be found intuitively when there are many configurations in the future). This namespace is the id of the seata we just created. You can't enter it indiscriminately, otherwise the nacos configuration file doesn't exist


If you don't have git, you can download and install it, and the execution looks successful

Where config Txt is the configuration file, which is called nacos config The function of SH is to inject the attributes of the configuration file into nacos, so there is no need to inject the file Conf and registry Conf is put into our project. If config. Is not found Txt will be placed in the root directory of seata, and the configuration will be read directly from nacos. As shown in the figure:

Then go to the nacos console

You can see the successful loading

3. Project integration seata

  • Database preparation:
    Three data: seata_account,seata_order,seata_storage
    sql statement:
CREATE DATABASE seata_order;
 
CREATE DATABASE seata_storage;
 
CREATE DATABASE seata_account;

When creating a table, pay attention to distinguishing between databases:
t_order:

CREATE TABLE t_order (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT(11) DEFAULT NULL COMMENT 'user id',
  `product_id` BIGINT(11) DEFAULT NULL COMMENT 'product id',
  `count` INT(11) DEFAULT NULL COMMENT 'quantity',
  `money` DECIMAL(11,0) DEFAULT NULL COMMENT 'amount of money',
  `status` INT(1) DEFAULT NULL COMMENT 'Order status: 0: being created; 1: Closed' 
) 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 'product id',
 `total` INT(11) DEFAULT NULL COMMENT 'Total inventory',
 `used` INT(11) DEFAULT NULL COMMENT 'Used inventory',
 `residue` INT(11) DEFAULT NULL COMMENT 'Remaining inventory'
) 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 'user id',
  `total` DECIMAL(10,0) DEFAULT NULL COMMENT 'Total amount',
  `used` DECIMAL(10,0) DEFAULT NULL COMMENT 'Balance used',
  `residue` DECIMAL(10,0) DEFAULT '0' COMMENT 'Remaining available limit'
) 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;

Finally, create undo under each database_ Log table:
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.
-- This script must be initialized in your current business database for AT pattern XID record. And server End independent (Note: business database)
-- Note here 0.3.0+ Add unique index 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;
 


After successful construction:

  • New order module seata-order-service2001
  • pom dependency
<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>
                <!--   Exclude default-->
                <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>
  • View dependencies
  • Modify application YML profile
server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
#      seata:
#        #The user-defined transaction group name needs to correspond to that in 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 no namespace specified. Default 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 #Special attention should be paid here to maintaining the consistency with the configuration in 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 default 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. The namespace ID in 2 is configured, but the default public space is not configured
  service:
    vgroup-mapping:
      my_test_tx_group: default		# Here, we should pay special attention to maintaining the consistency with the configuration in nacos





So it's public, otherwise it will appear

No available service


  • Entity classes Order and CommonResult
    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; //Order status: 0: being created; 1: Closed
}

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);
    }
}

  • The dao layer creates the OrderDao interface
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 {
    /**
     * Create order
     * @param order
     */
    void createOrder(Order order);

    /**
     * Update order
     * @param userId
     * @param status
     */
    void updateStatus(@Param("userId") Long userId, @Param("status") Integer status);
}

  • service layer: OrderService, StorageService, AccountService

OrderService

package com.zhubayi.springcloud.alibaba.service;

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

/**
 * @author zhubayi
 */
public interface OrderService {
    /**
     * Create order
     * @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);
}

Create the implementation class OrderServiceImpl of orderService

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;
    /**
     * Create order
     *
     * @param order
     */
    @Override
    public void createOrder(Order order) {
        log.info("=====>{}","Start new order");
        orderDao.createOrder(order);
        log.info("=====>{}","New order successfully created");
        log.info("=====>{}","Inventory reduction");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("=====>{}","Inventory reduction succeeded");
        log.info("=====>{}","Start reducing balance");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("=====>{}","Balance reduction succeeded");

        log.info("=====>{}","Start modifying order status");
        orderDao.updateStatus(order.getUserId(),0);
        log.info("=====>{}","Order status modified successfully");

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

  • Create 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,"Created successfully");
    }
}

Create the mapper directory under the resources directory, and then create orderdao XML file

<?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>
  • Main startup class 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);
    }
}

The finished directory looks like:

  • Create a new inventory module seata-storage-service2002
  • pom file dependency
 <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>
  • Modify the configuration file application YML, which is almost the same as the previous order module seata-order-service2001. The port and name are different. The database is changed to 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		#Special attention should be paid here to maintaining the consistency with the configuration in 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. The namespace ID in 2 is configured, but the default public space is not configured
  service:
    vgroup-mapping:
      my_test_tx_group: default		# Special attention should be paid here to maintaining the consistency with the configuration in nacos



  • Entity class Storage
    Storage:
package com.zhubayi.springcloud.alibaba.domain;

import lombok.Data;

@Data
public class Storage {

    private Long id;

    /**
     * Product id
     */
    private Long productId;

    /**
     * Total inventory
     */
    private Integer total;

    /**
     * Used inventory
     */
    private Integer used;

    /**
     * Remaining inventory
     */
    private Integer residue;
}

CommonResult is the same as the order module

  • service layer StorageService
package com.zhubayi.springcloud.alibaba.service;


public interface StorageService {
    /**
     * Deduct inventory
     */
    void decrease(Long productId, Integer count);
}

The implementation class of StorageService is 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;

    /**
     * Deduct inventory
     */
    @Override
    public void decrease(Long productId, Integer count) {
        LOGGER.info("------->storage-service Start of inventory deduction");
        storageDao.decrease(productId,count);
        LOGGER.info("------->storage-service End of inventory deduction");
    }
}

  • StorageDao interface:
package com.zhubayi.springcloud.alibaba.dao;

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

@Mapper
public interface StorageDao {

    /**
     *Deduct inventory
     **/
    void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}

StorageDao.xml file

<?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 class
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;

    /**
     * Deduct inventory
     */
    @RequestMapping("/storage/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId, count);
        return new CommonResult(200,"Inventory deduction succeeded!");
    }
}

  • Main startup class: 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);
    }
}

After completion:

  • Create Seata account service2003 user balance module
  • pom file dependency
 <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>
  • New profile 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		#Special attention should be paid here to maintaining the consistency with the configuration in 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. The namespace ID in 2 is configured, but the default public space is not configured
  service:
    vgroup-mapping:
      my_test_tx_group: default		# Special attention should be paid here to maintaining the consistency with the configuration in nacos



Note the database name

  • Entity class 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;

    /**
     * User id
     */
    private Long userId;

    /**
     * Total amount
     */
    private BigDecimal total;

    /**
     * Used limit
     */
    private BigDecimal used;

    /**
     * Remaining quota
     */
    private BigDecimal residue;
}

CommonResult is the same as before

  • AccountDao interface
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 {

    /**
     * Deduction of account balance
     * @param userId
     * @param money
     */
    void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
}

  • AccountService interface
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 {

    /**
     * Deduction of account balance
     * @param userId User id
     * @param money amount of money
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

  • Implementation class of AccountService interface 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;

/**
 * Account business implementation class
 */
@Service
public class AccountServiceImpl implements AccountService {

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


    @Resource
    AccountDao accountDao;

    /**
     * Deduction of account balance
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
        LOGGER.info("------->account-service Start of deduction of account balance in");
        //Simulation timeout exception, global transaction rollback
        //Pause the thread for a few seconds
       // try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service End of account balance deducted in");
    }
}

AccountController class

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;

    /**
     * Deduction of account balance
     */
    @RequestMapping("/account/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
        accountService.decrease(userId,money);
        return new CommonResult(200,"Account balance deducted successfully!");
    }
}

  • Start class 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 file

<?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>

Start three modules as well as nacos and seata

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]`

Note that the configuration is correct only when the yellow part appears, and nacos is registered

Open the browser and enter: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100

You can see that the order is created successfully, and then look at the database:

At this time, we manually create a timeout exception. By default, openfeign is called remotely for 1 second, and an error is reported if no data is returned

Visit again: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100

Look at the console again:
The order module reports a timeout exception. There is an order in the database, but the status of the order has not changed. Distributed transactions are used here.

To solve this problem, we need to add @ GlobalTransactiona annotation to the method of distributed transaction

Then browse and visit: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100

Take another look at the console:

It is also a timeout exception

Go check the database

The order table is not added, and the accout and storage tables are not changed. It shows that the problem of distributed transaction is solved
In order to see in more detail, let's make a breakpoint

Call again http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100

  • One more piece of data

Inventory deduction succeeded

View global lock, branch lock and table data lock
View seata library table lock status information



  • View the order library, storage library and undo_log rollback log

  • Release the breakpoint, let the code logic execute and check the rollback.
    Code execution result: account balance deduction timeout, transaction failed.


    Undo after rollback_ Log, all lock information is cleared

    At this point, the verification is completed successfully
    Reference blog:
    Blog 1
    Blog 2

Topics: Java MySQL Spring Back-end