Seata 是什么?
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。
關(guān)于Seata的介紹和事務(wù)的詳細(xì)流轉(zhuǎn)細(xì)節(jié)參考 Seata官網(wǎng)
本文采用docker部署seata服務(wù)
1、運(yùn)行鏡像
docker run --name seata-server -p 8091:8091 -d seataio/seata-server
2、復(fù)制配置文件到主機(jī) 當(dāng)前目錄
docker cp seata-server:/seata-server .
3、停止服務(wù)
docker stop seata-server
4、刪除服務(wù)
docker rm seata-server
5、重新運(yùn)行服務(wù)
# 腳本
# BEGIN ANSIBLE MANAGED BLOCK
#!/bin/bash
HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker rm -f seata-server;
docker run --name seata-server \
--restart=always \
-v $HOME/seata-server:/seata-server \
-e SEATA_IP=192.168.8.43 \
-e SEATA_PORT=8091 \
-p 8091:8091 \
-d seataio/seata-server
# END ANSIBLE MANAGED BLOCK
6、切換到seata配置文件目錄
cd /seata-server/resources
修改register.conf
- 如果是配置中心是file的話,會(huì)使用file.conf里面的配置,如果是其他,使用配置中心的配置
- 直連 eureka/consul/apollo/etcd/zookeeper/sofa/redis/file
本文采用nacos配置
- 修改 register.type 和 config.type 為 nacos
然后修改nacos的相關(guān)配置
# 注冊(cè)中心配置
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos" # 注冊(cè)類型
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server-lss"
serverAddr = "123.57.26.81:8848"
group = "SEATA_GROUP"
namespace = "lss_test"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
# 配置中心配置
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "123.57.26.81:8848"
namespace = "lss_test"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
7、重啟seata-server
docker restart seata-server
查看nacos,發(fā)現(xiàn)seata 服務(wù)端已正常啟動(dòng)

接下來配置項(xiàng)目
參考seata官網(wǎng)的賬戶、訂單項(xiàng)目
1、新建項(xiàng)目,如下圖

2、pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<!-- <relativePath />-->
<!-- <groupId>org.lss</groupId>-->
<!-- <artifactId>project</artifactId>-->
<!-- <version>1.0.0</version>-->
</parent>
<groupId>com.lss</groupId>
<artifactId>seata_xa</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<seata.version>1.4.0</seata.version>
<alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version>
</properties>
<modules>
<module>business_xa</module>
<module>order_xa</module>
<module>account_xa</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!-- durid -->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- <version>1.1.10</version>-->
<!-- </dependency>-->
<!--nacos動(dòng)態(tài)加載配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos服務(wù)注冊(cè)發(fā)現(xiàn)-->
<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>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-alibaba-seata</artifactId>-->
<!-- <version>2.0.0.RELEASE</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>io.seata</groupId>-->
<!-- <artifactId>seata-all</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<!-- <scope>runtime</scope>-->
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3、config.txt 和 nacos-config.sh是從 官網(wǎng) 拷出來的配置,需要上傳到nacos,具體也可參考/seata-server/resources/README-zh.md 里面的描述
- nacos-init.sh 是運(yùn)行 nacos-config.sh 的腳本
#-h: host, the default value is localhost.
#
#-p: port, the default value is 8848.
#
#-g: Configure grouping, the default value is 'SEATA_GROUP'.
#
#-t: Tenant information, corresponding to the namespace ID field of Nacos, the default value is ''.
#
#-u: username, nacos 1.2.0+ on permission control, the default value is ''.
#
#-w: password, nacos 1.2.0+ on permission control, the default value is ''.
bash nacos-config.sh -h 123.57.26.81 -p 8848 -g SEATA_GROUP -t lss_test -u nacos -w nacos
執(zhí)行后 bash nacos-init.sh 后,在nacos上可查看到相關(guān)配置

項(xiàng)目整體結(jié)構(gòu)圖

4、business_xa項(xiàng)目
- bootstrap.yml
server:
port: 17000
nacos:
namespace: lss_test
address: 123.57.26.81:8848
spring:
datasource:
url: jdbc:mysql://localhost:3306/xa_order?useSSL=false&serverTimezone=UTC
username: root
password: 12345678
driver-class-name: com.mysql.jdbc.Driver
application:
name: business_xa
cloud:
nacos:
namespace: ${nacos.namespace}
server-addr: ${nacos.address}
config:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
# file-extension: yml
# shared-configs:
# - data-id: common.yml
# refresh: true
discovery:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
register-enabled: true
# seata 配置
seata:
# 注冊(cè)信息
registry:
type: nacos
nacos:
application: seata-server-lss # 這個(gè)是 seata 服務(wù)端的應(yīng)用名稱
server-addr: ${nacos.address} # nacos 服務(wù)地址
group : "SEATA_GROUP" # nacos 分組
namespace: ${nacos.namespace} # nacos 命名空間
username: "nacos"
password: "nacos"
# 配置信息
config:
type: nacos
nacos:
server-addr: 123.57.26.81:8848
group: "SEATA_GROUP"
namespace: ${nacos.namespace}
username: "nacos"
password: "nacos"
application-id: ${spring.application.name}
tx-service-group: seata-server_xa_test # 自定義事物組 tc
- BusinessController.java
package com.lss.sample.operator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("api")
@RestController
@Slf4j
public class BusinessController {
@Autowired
BusinessService businessService;
@GetMapping("purchase")
public String purchase(@RequestParam(value = "type", defaultValue = "1") Integer type ) {
try {
businessService.purchase(type);
} catch (Exception exx) {
log.info("異常:{}", exx);
return "Purchase Failed:" + exx.getMessage();
}
return "SUCCESS";
}
}
- BusinessService.java
package com.lss.sample.operator;
import com.lss.sample.feign.AccountFeignClient;
import com.lss.sample.feign.OrderFeignClient;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class BusinessService {
final String SUCCESS = "SUCCESS";
@Autowired
private OrderFeignClient orderFeignClient;
@Autowired
AccountFeignClient accountFeignClient;
@GlobalTransactional(rollbackFor = Exception.class)
public void purchase(Integer type) {
String xid = RootContext.getXID();
log.info("business-xid:{}", xid);
String accountResult = accountFeignClient.add();
throw new RuntimeException("賬戶服務(wù)調(diào)用失敗,事務(wù)回滾!");
if (!SUCCESS.equals(accountResult)) {
throw new RuntimeException("賬戶服務(wù)調(diào)用失敗,事務(wù)回滾!");
}else {
log.info("賬戶服務(wù)調(diào)用成功...");
}
//
String orderResult = orderFeignClient.create(type);
if (!SUCCESS.equals(orderResult)) {
log.info("訂單服務(wù)調(diào)用失敗...");
throw new RuntimeException("訂單服務(wù)調(diào)用失敗,事務(wù)回滾!");
}else {
log.info("訂單服務(wù)調(diào)用成功...");
}
}
}
- AccountFeignClient.java
package com.lss.sample.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "AccountFeignClient", url = "127.0.0.1:17002")
public interface AccountFeignClient {
@GetMapping("api/add")
String add();
}
- OrderFeignClient.java
package com.lss.sample.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "OrderFeignClient", url = "127.0.0.1:17001")
public interface OrderFeignClient {
@GetMapping("api/create")
String create(@RequestParam(value = "type") Integer type);
}
- 啟動(dòng)類 BusinessXAApplication.java
package com.lss.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class BusinessXAApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessXAApplication.class, args);
}
}
5、account_xa項(xiàng)目
- account_xa服務(wù)的bootstrap.yml
server:
port: 17002
nacos:
namespace: lss_test
address: 123.57.26.81:8848
spring:
datasource:
url: jdbc:mysql://localhost:3306/xa_account?useSSL=false&serverTimezone=UTC
username: root
password: 12345678
# type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
application:
name: account_xa
cloud:
nacos:
namespace: ${nacos.namespace}
server-addr: ${nacos.address}
config:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
# file-extension: yml
# shared-configs:
# - data-id: common.yml
# refresh: true
discovery:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
register-enabled: true
# seata 配置
seata:
# 注冊(cè)信息
registry:
type: nacos
nacos:
application: seata-server-lss # 這個(gè)是 seata 服務(wù)端的應(yīng)用名稱
server-addr: ${nacos.address} # nacos 服務(wù)地址
group : "SEATA_GROUP" # nacos 分組
namespace: ${nacos.namespace} # nacos 命名空間
username: "nacos"
password: "nacos"
# 配置信息
config:
type: nacos
nacos:
server-addr: 123.57.26.81:8848
group: "SEATA_GROUP"
namespace: ${nacos.namespace}
username: "nacos"
password: "nacos"
application-id: ${spring.application.name}
tx-service-group: seata-server_xa_test # 自定義事物組 tc
- AccountController.java
package com.lss.sample.operator;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api")
@Slf4j
public class AccountController {
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("add")
public String add() {
String xid = RootContext.getXID();
log.info("account-xid:{}", xid);
jdbcTemplate.update("update account set account = 10000 where id = 4 ");
return "SUCCESS";
}
}
- DataSourceConfig.java
package com.lss.sample.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.rm.datasource.xa.DataSourceProxyXA;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
// DataSourceProxy for AT mode
// return new DataSourceProxy(druidDataSource);
// DataSourceProxyXA for XA mode
return new DataSourceProxyXA(druidDataSource);
}
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {
return new JdbcTemplate(dataSourceProxy);
}
}
- 啟動(dòng)類 AccountXAApplication
package com.lss.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableFeignClients
@EnableTransactionManagement
public class AccountXAApplication {
public static void main(String[] args) {
SpringApplication.run(AccountXAApplication.class, args);
}
}
6、order_xa項(xiàng)目
- bootstrap.yml
server:
port: 17001
nacos:
namespace: lss_test
address: 123.57.26.81:8848
spring:
datasource:
url: jdbc:mysql://localhost:3306/xa_order?useSSL=false&serverTimezone=UTC
username: root
password: 12345678
driver-class-name: com.mysql.jdbc.Driver
application:
name: order_xa
cloud:
nacos:
namespace: ${nacos.namespace}
server-addr: ${nacos.address}
config:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
# file-extension: yml
# shared-configs:
# - data-id: common.yml
# refresh: true
discovery:
enabled: true
namespace: ${spring.cloud.nacos.namespace}
register-enabled: true
# seata 配置
seata:
# 注冊(cè)信息
registry:
type: nacos
nacos:
application: seata-server-lss # 這個(gè)是 seata 服務(wù)端的應(yīng)用名稱
server-addr: ${nacos.address} # nacos 服務(wù)地址
group : "SEATA_GROUP" # nacos 分組
namespace: ${nacos.namespace} # nacos 命名空間
username: "nacos"
password: "nacos"
# 配置信息
config:
type: nacos
nacos:
server-addr: 123.57.26.81:8848
group: "SEATA_GROUP"
namespace: ${nacos.namespace}
username: "nacos"
password: "nacos"
application-id: ${spring.application.name}
tx-service-group: seata-server_xa_test # 自定義事物組 tc
- OrderController.java
package com.lss.sample.operator;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("api")
@RestController
@Slf4j
public class OrderController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("create")
public String create(@RequestParam(value = "type") Integer type) {
String xid = RootContext.getXID();
log.info("account-xid:{}", xid);
try {
deal(type);
} catch (Exception e) {
return "FAIL";
}
return "SUCCESS";
}
@Transactional
public void deal(Integer type) {
jdbcTemplate.update("update `order` set num = 100 where id = 1 ");
if (type.equals(2)) {
log.info("order 調(diào)用異常...");
throw new RuntimeException("order 調(diào)用異常...");
}
}
}
- DataSourceConfig
package com.lss.sample.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.xa.DataSourceProxyXA;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
// DataSourceProxy for AT mode
// return new DataSourceProxy(druidDataSource);
// DataSourceProxyXA for XA mode
return new DataSourceProxyXA(druidDataSource);
}
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {
return new JdbcTemplate(dataSourceProxy);
}
}
- 啟動(dòng)類OrderXAApplication.java
package com.lss.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderXAApplication {
public static void main(String[] args) {
SpringApplication.run(OrderXAApplication.class, args);
}
}
運(yùn)行項(xiàng)目
1、business_xa、account_xa、order_xa依次運(yùn)行后,在nacos上發(fā)現(xiàn)項(xiàng)目都已經(jīng)注冊(cè)上去了,但是項(xiàng)目會(huì)報(bào)一個(gè)錯(cuò)誤
no available service 'null' found, please make sure registry config correct
報(bào)錯(cuò)原因在這個(gè)類里 io.seata.core.rpc.netty.NettyClientChannelManager
根據(jù)seata導(dǎo)入客戶端相關(guān)配置后
添加一個(gè)配置
- service.vgroupMapping.(自定義的事務(wù)組名稱 tc)=default 就可以了
本文的配置為:service.vgroupMapping.seata-server_xa_test = default
nacos上如下圖
image.png
2、在數(shù)據(jù)庫(kù)中添加sql腳本
- xa_account 庫(kù)
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account` varchar(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
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=12 DEFAULT CHARSET=utf8;
- xa_order 庫(kù)
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
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=12 DEFAULT CHARSET=utf8;
3、至此,seata集成已經(jīng)完成了,啟動(dòng)項(xiàng)目后,訪問business_xa服務(wù)
curl http://localhost:17000/api/purchase?type=1
發(fā)現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)被正常更新
重新手動(dòng)更新數(shù)據(jù)后,再次訪問
curl http://localhost:17000/api/purchase?type=2
發(fā)現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)回滾
