官网快速启动
用例说明
用例
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
简单来说就是:下订单–>扣库存–>减账户(余额)
架构图

准备工作
库表
-
建立数据库
要求:具有InnoDB引擎的MySQL。
注意: 实际上,在示例用例中,这3个服务应该有3个数据库。 但是,为了简单起见,我们只创建一个数据库并配置3个数据源。
-
创建 UNDO_LOG 表
使用脚本:seata-1.3.0/script/client/at/db/mysql.sql,脚本如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
|
-
为示例业务创建表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 初始化库存模拟数据
delete from seata_demo.storage_tbl;
delete from seata_demo.order_tbl;
delete from seata_demo.account_tbl;
INSERT INTO seata_demo.storage_tbl (commodity_code, count) VALUES ('product-1', 10);
INSERT INTO seata_demo.account_tbl (user_id, money) VALUES ('david001', 100);
|
工程
先演示没有分布式事务控制时的异常情况
- cloudalibaba-seata-business2001:业务逻辑
- cloudalibaba-seata-service2002:模拟3个业务分别操作3个数据源,分别是对给定的商品扣除仓储数量,根据采购需求创建订单,从用户帐户中扣除余额
工程调用关系:

cloudalibaba-seata-service2002
工程目录结构

pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<?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>cloud2020</artifactId>
<groupId>com.eh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-seata-storage2002</artifactId>
<dependencies>
<!--服务注册与发现nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--整合web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入通用包,主要使用CommonResult类-->
<dependency>
<groupId>org.eh</groupId>
<artifactId>cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--数据源配置 begin-->
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--数据源配置 end-->
</dependencies>
</project>
|
yml
使用3个数据源模拟3个数据库的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
server:
port: 2002
spring:
application:
name: cloudalibaba-seata-service2002
cloud:
nacos:
discovery:
#配置nacos地址
server-addr: localhost:8848
datasource:
storage:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
# springboot2.0以上配置多数据源,需要将url改成jdbc-url
# spring.datasource.url 数据库的 JDBC URL
# spring.datasource.jdbc-url 用来重写自定义连接池
# 官方给出的解释是:因为连接池的实际类型没有被公开,所以在您的自定义数据源的元数据中没有生成密钥,
# 而且在IDE中没有完成(因为DataSource接口没有暴露属性)。另外,如果您碰巧在类路径上有Hikari,那么这个基本设置就不起作用了,
# 因为Hikari没有url属性(但是确实有一个jdbcUrl属性)。在这种情况下,您必须重写您的配置
jdbc-url: jdbc:mysql://localhost:3306/seata_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 333
order:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
jdbc-url: jdbc:mysql://localhost:3306/seata_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 333
account:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
jdbc-url: jdbc:mysql://localhost:3306/seata_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 333
|
业务类
数据源配置
dao层
-
AccountMapper
1
2
3
4
5
6
7
8
9
10
|
package com.eh.cloud.seata.service.dao.account;
import com.eh.cloud.seata.service.entity.Account;
import org.apache.ibatis.annotations.Update;
public interface AccountMapper {
@Update("update account_tbl set money = money - #{money} where user_id = #{userId}")
int decreateMoney(Account account);
}
|
-
OrderMapper
1
2
3
4
5
6
7
8
9
10
|
package com.eh.cloud.seata.service.dao.order;
import com.eh.cloud.seata.service.entity.Order;
import org.apache.ibatis.annotations.Insert;
public interface OrderMapper {
@Insert("insert into order_tbl(user_id, commodity_code, count, money) values(#{userId}, #{commodityCode}, #{count}, #{money})")
int createOrder(Order order);
}
|
-
StorageMapper
1
2
3
4
5
6
7
8
9
10
11
|
package com.eh.cloud.seata.service.dao.storage;
import com.eh.cloud.seata.service.entity.Storage;
import org.apache.ibatis.annotations.Update;
public interface StorageMapper {
@Update("update storage_tbl set count = count - #{count} where commodity_code = #{commodityCode}")
int decreateStorage(Storage storage);
}
|
主启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package com.eh.cloud.seata.service;
import com.eh.cloud.seata.service.dao.account.AccountMapper;
import com.eh.cloud.seata.service.dao.order.OrderMapper;
import com.eh.cloud.seata.service.dao.storage.StorageMapper;
import com.eh.cloud.seata.service.entity.Account;
import com.eh.cloud.seata.service.entity.Order;
import com.eh.cloud.seata.service.entity.Storage;
import com.eh.cloud2020.common.entity.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
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.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SeataStorageMain2002 {
public static void main(String[] args) {
SpringApplication.run(SeataStorageMain2002.class, args);
}
@RestController
class StorageController {
@Autowired
private StorageMapper storageMapper;
@PostMapping("/storage/decrease")
public CommonResult<String> decreateStorage(@RequestBody Map<String, String> request) {
try {
Storage storage = new Storage();
storage.setCommodityCode(request.get("commodityCode"));
storage.setCount(Integer.valueOf(request.get("count")));
int i = storageMapper.decreateStorage(storage);
return CommonResult.success("成功更新:" + i);
} catch (Exception e) {
return CommonResult.error(80001, "库存服务出错");
}
}
}
@RestController
class OrderController {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountMapper accountMapper;
@PostMapping("/order/create")
public CommonResult<String> createOrder(@RequestBody Map<String, String> request) {
try {
// 创建订单
Order order = new Order();
order.setCommodityCode(request.get("userId"));
order.setCommodityCode(request.get("commodityCode"));
order.setCount(Integer.valueOf(request.get("count")));
order.setMoney(Integer.valueOf(request.get("money")));
int i = orderMapper.createOrder(order);
// 扣减余额
Account account = new Account();
account.setUserId(request.get("userId"));
account.setMoney(Integer.valueOf(request.get("money")));
// 演示异常情况时打开
int m = 1 / 0;
int j = accountMapper.decreateMoney(account);
return CommonResult.success("成功创建订单并扣减余额:" + (i + j));
} catch (Exception e) {
return CommonResult.error(80002, "订单服务出错");
}
}
}
}
|
cloudalibaba-seata-business2001
工程目录结构

pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
<?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>cloud2020</artifactId>
<groupId>com.eh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-seata-business2001</artifactId>
<dependencies>
<!--整合openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--服务注册与发现nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入通用包,主要使用CommonResult类-->
<dependency>
<groupId>org.eh</groupId>
<artifactId>cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入jdbc starter和数据库驱动,做数据初始化使用,也可以不加以下依赖,自己观察数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
|
yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
server:
port: 2001
spring:
application:
name: cloudalibaba-seata-business2001
cloud:
nacos:
discovery:
#配置nacos地址
server-addr: localhost:8848
datasource:
username: root
password: 333
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_demo?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&allowMultiQueries=true
initialization-mode: always
schema: classpath:init.sql
#设置feign客户端超时时间(ribbon默认超时时间1s,测试过程中有超过1s的情况,不利于测试,改成3s)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 3000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 3000
|
init.sql
1
2
3
4
5
6
|
-- 初始化库存模拟数据
delete from seata_demo.storage_tbl;
delete from seata_demo.order_tbl;
delete from seata_demo.account_tbl;
INSERT INTO seata_demo.storage_tbl (commodity_code, count) VALUES ('product-1', 10);
INSERT INTO seata_demo.account_tbl (user_id, money) VALUES ('david001', 100);
|
logback.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<?xml version="1.0" encoding="UTF-8"?>
<configuration scanPeriod="60 seconds" debug="false">
<timestamp key="dt" datePattern="yyyyMMdd HH:mm:ss"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--高亮-->
<pattern>${dt} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n</pattern>
</encoder>
<withJansi>true</withJansi>
</appender>
<!-- <logger name="com.eh.springcloud.payment8001.com.eh.cloud.seata.storage.dao" level="debug"/>
<logger name="org.mybatis" level="debug"/>-->
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
|
业务类
SeataService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.eh.cloud.seata.business.third;
import com.eh.cloud2020.common.entity.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.Map;
@FeignClient("cloudalibaba-seata-service2002")
public interface SeataService {
@PostMapping("/account/decrease")
CommonResult<String> decreateAccount(Map<String, String> request);
@PostMapping("/order/create")
CommonResult<String> createOrder(Map<String, String> request);
@PostMapping("/storage/decrease")
CommonResult<String> decreateStorage(Map<String, String> request);
}
|
BusinessService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.eh.cloud.seata.business.service;
import com.eh.cloud.seata.business.third.SeataService;
import com.eh.cloud2020.common.entity.CommonResult;
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.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class BusinessService {
@Autowired
private SeataService seataService;
public void test() {
log.info("=========>开始测试");
log.info("用户====>用户id:{},余额:{}", "david001", "100");
log.info("库存====>商品编号:{},商品剩余数量:{}", "product-1", "10");
// 减库存
Map<String, String> storageReq = new HashMap() {{
put("commodityCode", "product-1");
put("count", "1");
}};
log.info("==========>开始扣减库存");
CommonResult<String> storageRet = seataService.decreateStorage(storageReq);
// 建订单
Map<String, String> orderReq = new HashMap() {{
put("userId", "david001");
put("commodityCode", "product-1");
put("count", "1");
put("money", "25");
}};
log.info("==========>开始创建订单");
CommonResult<String> orderRet = seataService.createOrder(orderReq);
if (orderRet.isOk() && storageRet.isOk()) {
log.info("========>用户购买商品成功");
} else {
throw new RuntimeException("用户购买商品失败");
}
}
}
|
主启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.eh.cloud.seata.business;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SeataBusinessMain2001 {
public static void main(String[] args) {
SpringApplication.run(SeataBusinessMain2001.class, args);
}
}
|
测试类
SeataBusinessTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package com.eh.cloud.seata.business;
import com.eh.cloud.seata.business.service.BusinessService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.Map;
@Slf4j
@SpringBootTest
public class SeataBusinessTest {
@Autowired
private BusinessService businessService;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test() {
businessService.test();
}
@Test
public void queryResult() {
// 查询数据库变化情况
log.info("=============> 结果查询");
Map<String, Object> storateResult = jdbcTemplate.queryForMap("select commodity_code, count from storage_tbl limit 1");
log.info("库存情况==========>产品:{},剩余数量:{}", storateResult.get("commodity_code"), storateResult.get("count"));
Map<String, Object> orderResult = jdbcTemplate.queryForMap("select id, commodity_code, count, money from order_tbl limit 1");
log.info("订单情况==========>创建订单, id:{},产品编号:{},购买数量:{}", orderResult.get("id"), orderResult.get("commodity_code"), orderResult.get("count"));
Map<String, Object> accountResult = jdbcTemplate.queryForMap("select user_id, money from account_tbl limit 1");
log.info("用户余额情况==========>用户:{},余额:{}", accountResult.get("user_id"), accountResult.get("money"));
}
}
|
执行结果:

从日志中可以看到库存减少了1,订单也成功创建,但是用户余额没有扣减。接下来我们加入seata框架到应用中来处理分布式事务。
引入Seata处理分布式事务
pom依赖说明:部署指南
yml配置内容:进入 资源目录 seata/script/client/spring/
,展示的就是 seata 整合 Spring 的全部配置内容,提供了 .properties
、.yml
两种格式的配置。详细的配置项还挺多,此处就不粘贴了,你可以点击 资源目录 查看。
分布式事务处理过程

服务调用版本
改造cloudalibaba-seata-service2002
pom,引入seta
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!--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>
<!--我的服务器端版本是1.2.0-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
|
yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
# seata服务
service:
vgroup-mapping:
my_test_tx_group: default # 此处key需要与tx-service-group的value一致,否则会报 no available service 'null' found, please make sure registry config correct 异常
grouplist:
default: localhost:8091
enable-degrade: false
disable-global-transaction: false
# seata配置,使用nacos作为配置中心
config:
type: nacos
nacos:
namespace:
serverAddr: localhost:8848
group: SEATA_GROUP
userName: ""
password: ""
# seata服务所在注册中心
registry:
type: nacos
nacos:
application: seata-server # 此处名称需和 seata server 服务端 application一致,否则会报 no available service 'null' found, please make sure registry config correct 异常
server-addr: localhost:8848
namespace:
userName: ""
password: ""
|
cloudalibaba-seata-business2001,pom和yml 引入seta依赖和配置,同上面cloudalibaba-seata-business2002
在测试方法上增加注解@GlobalTransactional 即可使用seata的分布式事务功能
1
2
|
@GlobalTransactional
public void test() {
|
执行测试方法,然后在数据库执行如下查询:
1
2
3
|
select * from seata_demo.storage_tbl;
select * from seata_demo.order_tbl;
select * from seata_demo.account_tbl;
|
可以发现数据保持一致。
多数据源版本
pom和yaml配置不变,增加以下测试类
业务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package com.eh.cloud.seata.service;
import com.eh.cloud.seata.dao.account.AccountMapper;
import com.eh.cloud.seata.dao.order.OrderMapper;
import com.eh.cloud.seata.dao.storage.StorageMapper;
import com.eh.cloud.seata.entity.Account;
import com.eh.cloud.seata.entity.Order;
import com.eh.cloud.seata.entity.Storage;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private StorageMapper storageMapper;
@Autowired
private OrderMapper orderMapper;
@GlobalTransactional
public void placeOrder() {
log.info("=========>开始测试");
log.info("用户====>用户id:{},余额:{}", "david001", "100");
log.info("库存====>商品编号:{},商品剩余数量:{}", "product-1", "10");
// 减库存
Storage storage = new Storage();
storage.setCommodityCode("product-1");
storage.setCount(1);
log.info("==========>开始扣减库存");
storageMapper.decreateStorage(storage);
// 建订单
Order order = new Order();
order.setUserId("david001");
order.setCommodityCode("product-1");
order.setCount(1);
order.setMoney(25);
log.info("==========>开始创建订单");
orderMapper.createOrder(order);
// 扣减余额
Account account = new Account();
account.setUserId("david001");
account.setMoney(25);
// 演示异常情况时打开
int m = 1 / 0;
log.info("==========>开始扣减余额");
accountMapper.decreateMoney(account);
}
}
|
controller
1
2
3
4
5
6
7
|
@Autowired
private OrderService orderService;
@GetMapping("/test")
public void test() {
orderService.placeOrder();
}
|
测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
GET http://localhost:2002/test
HTTP/1.1 500
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 17 Nov 2020 13:17:07 GMT
Connection: close
{
"timestamp": "2020-11-17T13:17:07.947+0000",
"status": 500,
"error": "Internal Server Error",
"message": "/ by zero",
"path": "/test"
}
Response code: 500; Time: 446ms; Content length: 126 bytes
|
多次调用,查看数据库可以看到库存数量和用户余额不变,debug可以看到undo_log表和seta服务器配置的数据库中表的变化情况。