15. Chapter 15 of spring cloud Alibaba, upgrade, distributed transaction solution, Seata

Keywords: Programming Spring Database Mybatis MySQL

Chapter 14 of SpringCloud Alibaba, upgrade, distributed transaction solution, Seata

1, Overview of distributed transactions

1. What is distributed transaction

With the rapid development of the Internet, the software system has changed from the original single application to the distributed application.

The distributed system will split an application system into multiple services that can be deployed independently, so it needs remote cooperation between services to complete transaction operations. In this distributed system environment, the transaction completed by remote cooperation between different services through the network is called distributed transaction.

For example, user registration send points transaction, create order reduce inventory transaction, bank transfer transaction are distributed transactions.

1.1. The local transaction depends on the transaction features provided by the database itself to realize:

begin transaction;
	//1. Local database operation: Zhang San reduces the amount
	//2. Local database operation: Li Si increases the amount
commit transation;

1.2. However, in a distributed environment, it will become the following:

begin transaction;
	//1. Local database operation: Zhang San reduces the amount
	//2. Remote call: let Li Si increase the amount
commit transation;

It can be imagined that when the remote call makes Li Si increase the amount successfully, and the remote call fails to return due to network problems, the local transaction submission failure rolls back Zhang San's operation of reducing the amount, and the data of Zhang San and Li Si are inconsistent.

Therefore, on the basis of the distributed architecture, the traditional database transaction can not be used. The accounts of Zhang San and Li Si are not in a database or even an application system. To realize the transfer transaction, it needs to be called remotely. Because of the network problem, the distributed transaction problem will be caused.

2. Distributed transaction generation scenario:

2.1. A typical scenario is that microservices in microservice architecture complete transaction operations through remote calls. For example: order micro service and inventory micro service, when placing an order, the order micro service requests inventory micro service to reduce inventory.

2.2 when a single system needs to access multiple database instances, distributed transactions will be generated. For example, the user information and order information are stored in two MySQL instances respectively. When the user management system deletes the user information, it needs to delete the user information and the user order information respectively. Because the data is distributed in different data instances, it needs to operate the data through different database links. At this time, a distributed transaction is generated.

2.3. Multi service access to the same database instance, for example: even if the order micro service and inventory micro service access to the same database, they will generate distributed transactions. The reason is that the two micro services hold different database links for database operations across the JVM process. At this time, distributed transactions are generated.

2, Seata overview

1. Introduction

Seata is an open source distributed transaction solution dedicated to providing high-performance and easy-to-use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes, and build a one-stop distributed solution for users.

2. Terminology

XID - Transaction ID, the only ID of a distributed transaction

TC - Transaction Coordinator
 Maintain the state of global and branch transactions, and drive global transaction commit or rollback.

TM - transaction manager
 Define the scope of global transaction: start global transaction, commit or roll back global transaction.

RM - Resource Manager
 Manage the resources of branch transaction, talk with TC to register branch transaction and report the status of branch transaction, and drive branch transaction commit or rollback.

3. Seata process flow

1. TM applies to TC to open a global transaction. The global transaction is created successfully and a globally unique ID, XID, is generated
 2. XID propagates across the context of the microservice call link
 3. RM registers branch transactions with TC and puts them under the jurisdiction of global transactions corresponding to XID
 4. TM initiates a global transaction commit or rollback resolution for XID to TC
 5. TC schedules all branch transactions under XID to complete the commit or rollback request

other:

Official website: http://seata.io/

Download address: http://seata.io/zh-cn/blog/download.html

Use: @ GlobalTransactional

3, Seata Server installation

1. Unzip seata-server-0.9.0.zip

2. Enter the conf directory to modify the file.conf file: user defined transaction group name, transaction storage mode, database connection information


service module custom transaction group name:
vgroup_mapping.my_test_tx_group = "default"
Modify it to vgroup "mapping. My" test "TX" group = "seatatxgroup"

The store module modifies the transaction storage mode:
mode = "file"
Modify to mode="db"

Modify db module to your database connection:
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "admin123"


3. Create the corresponding database and table according to the above database connection

CREATE SCHEMA `seata` DEFAULT CHARACTER SET utf8 ;

The SQL of the corresponding table is in the db_store.sql file in the conf Directory:
Global table, branch table and lock table

4. Modify the registry.conf file under conf to register seata in the corresponding registry

Change the type = "file" under the registration module to your corresponding registration center type="nacos"

At the same time, modify the corresponding registry module:
nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "seataTxGroup" ා 񖓿 ා - the corresponding transaction group name customized by seata
}

The file of the conf module is the file.conf file we modified. You can also modify the content of the conf registration information to nacos

Test:

1. Start nacos server with port number 8848

2. Start seata server

Check whether the nacos service list has the injection of seata server. The corresponding port is 8901

4, Seata case

Case study: transactions among three microservices: order inventory user amount account

1. Create order
 2. Modify the inventory corresponding to the goods purchased by the order,
3. And deduct the purchase amount from the user's account
 4. Last modify order status

1. Create corresponding microservice table

Order form:

CREATE DATABASE seata_order;

use seata_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 'Number',
    `money` DECIMAL(11,0) DEFAULT NULL COMMENT 'Amount of money',
    `status` INT(1) DEFAULT NULL COMMENT 'Order status: 0: Creating; 1: Closed'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
 
SELECT * FROM t_order;

an inventory statement:

CREATE DATABASE seata_storage;
 
use seata_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 stock',
    `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 t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');
 
 
SELECT * FROM t_storage;

Account table:

CREATE DATABASE seata_account;

use seata_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 'Used balance',
    `residue` DECIMAL(10,0) DEFAULT '0' COMMENT 'Remaining available quota'
) 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;

2. Create the corresponding rollback log table

##Each XID of swimsuit record needs to be set up in the order inventory account three databases

##The corresponding SQL is in DB undo log.sql under conf directory of seata server

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

3. Create corresponding micro service account

Create account micro service: seata-order-service-2003

<parent>
    <artifactId>cloud_2020</artifactId>
    <groupId>com.lee.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service-2003</artifactId>

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud_2020</artifactId>
        <groupId>com.lee.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>seata-order-service-2003</artifactId>

    <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>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</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>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </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>

</project>

application.yml

server:
  port: 2003

spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group #It is consistent with the name of the user-defined transaction group in file.conf under the sea TA server conf directory
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account
    username: root
    password: admin123

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory

Entity class

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;

    private Long userId;

    private BigDecimal total;

    private BigDecimal used;

    private BigDecimal residue;
}

@Data
@ToString
@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);
    }

}

Data layer

@Mapper
public interface AccountDao {

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

}

resource/mapper directory

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

    <resultMap id="BaseResultMap" type="com.lee.springcloud.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>

Service layer

public interface AccountService {

    //Deduction account balance
    void decrease( Long userId,BigDecimal money);
}

@Slf4j
@Service
public class AccountServiceImpl implements AccountService {

    @Resource
    private AccountDao accountDao;

    //Deduction account balance
    @Override
    public void decrease(Long userId, BigDecimal money) {

        log.info("------->account-service Start of the balance of the medium deduction account");
        //try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        log.info("------->account-service Ending of balance of China deduction account");
    }
}

controller

@RestController
public class AccountController {

    @Resource
    private AccountService accountService;

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

Configuration layer:

@Configuration
@MapperScan({"com.lee.springcloud.dao"})
public class MybatisConfig {
    
}

@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

Main startup class

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMainApp2003 {

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

4. Create corresponding micro service inventory

Create inventory microservice: seata-order-service-2002

<parent>
    <artifactId>cloud_2020</artifactId>
    <groupId>com.lee.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service-2002</artifactId>

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud_2020</artifactId>
        <groupId>com.lee.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>seata-order-service-2002</artifactId>

    <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>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</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>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

application.yml

server:
  port: 2002

spring:
  application:
    name: seata-storage-service
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group #It is consistent with the name of the user-defined transaction group in file.conf under the sea TA server conf directory
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_storage
    username: root
    password: admin123

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory

Entity class

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

}

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Storage {

    private Long id;

    // Product id
    private Long productId;

    //Total stock
    private Integer total;


    //Used inventory
    private Integer used;


    //Remaining inventory
    private Integer residue;
}

Data layer:

@Mapper
public interface StorageDao {

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

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

    <resultMap id="BaseResultMap" type="com.lee.springcloud.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>

Service layer

public interface StorageService {

    // Deduct inventory
    void decrease(Long productId, Integer count);
}

@Slf4j
@Service
public class StorageServiceImpl implements StorageService {

    @Resource
    private StorageDao storageDao;

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

controller

@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!");
    }
}

config

@Configuration
@MapperScan({"com.lee.springcloud.dao"})
public class MybatisConfig {

}

@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

Main startup class:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataStorageServiceApplication2002
{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataStorageServiceApplication2002.class, args);
    }
}

5. Create corresponding microservice - order

Create corresponding order microservice: seata-order-service-2001

<parent>
    <artifactId>cloud_2020</artifactId>
    <groupId>com.lee.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-service-2001</artifactId>

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud_2020</artifactId>
        <groupId>com.lee.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>seata-order-service-2001</artifactId>


    <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>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</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>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </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>


</project>

application.yml

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group #The user-defined transaction group name needs to correspond to that in sea TA server
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: admin123

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory

Entity class

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

}

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Order{
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //Order status: 0: creating; 1: closed
}

Data layer

@Mapper
public interface OrderDao{
    //New order
    void create(Order order);

    //Change order status from zero to 1
    void update(@Param("userId") Long userId, @Param("status") Integer status);
}

<?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.lee.springcloud.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.lee.springcloud.domain.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="create">
        insert into t_order (id,user_id,product_id,count,money,status)
        values (null,#{userId},#{productId},#{count},#{money},0);
    </insert>


    <update id="update">
        update t_order set status = 1
        where user_id=#{userId} and status = #{status};
    </update>

</mapper>

Service layer

public interface OrderService {

    void create(Order order);
}

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    /**
     * Create order - > call inventory service to deduct inventory - > call account service to deduct account balance - > Modify order status
     */

    @Override
    //@GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class)
    public void create(Order order){
        log.info("----->Start new order");
        //New order
        orderDao.create(order);

        //Deduct inventory
        log.info("----->Order micro service starts to call inventory and make deduction Count");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("----->Order micro service starts to call inventory and make deduction end");

        //Deduction account
        log.info("----->Order micro service starts to call account and make deduction Money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("----->Order micro service starts to call account and make deduction end");


        //Modify order status, from zero to 1 means completed
        log.info("----->Modify order status start");
        orderDao.update(order.getUserId(),0);
        log.info("----->Modify order status end");

        log.info("----->The order is over");

    }
}

feign

@FeignClient(value = "seata-storage-service")
public interface StorageService{

    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    
}

@FeignClient(value = "seata-account-service")
public interface AccountService{

    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    
}

controller

@RestController
public class OrderController{
    @Resource
    private OrderService orderService;


    @GetMapping("/order/create")
    public CommonResult create(Order order)
    {
        orderService.create(order);
        return new CommonResult(200,"Order created successfully");
    }
}

Configuration class

@Configuration
@MapperScan({"com.lee.springcloud.dao"})
public class MybatisConfig {

}

@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

Main startup class

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//Cancel configuration of data source auto creation
public class SeataOrderMainApp2001{

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

6. Testing

1. Start Nacos server, Seata server, Seata order service-2001 \ 2002 \ 2003

2. At this time, the order microservice does not open the global transaction
//@GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class)
The delay of the account micro service debit interface is not enabled
//try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
business as usual
 Browser call: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100
 result:
{"code":200,"message": "order created successfully", "data":null}

The tables corresponding to the database have been changed accordingly


3. Now we delay the account micro service debit interface, because the default call time of openFeign is 1s. If it exceeds 1s, the call fails
 Open the try {timeunit. Seconds. Sleep (20);} catch (interruptedexception E) {e.printstacktrace();} comment

Browser access: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100
 Error: java.net.socketimeoutexception: read timed out

Table corresponding to database:
Order table data is added, but status is 0
 Inventory table data modified used10 reside90
 Account table not moved

4. Now let's turn on global transactions
@GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class)

Browser access: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100

Error: java.net.socketimeoutexception: read timed out

Table corresponding to database:
No change

Posted by fatmcgav on Sat, 09 May 2020 02:57:10 -0700