在本篇文章中我們?cè)?code>SpringCloud環(huán)境下通過(guò)使用Seata
來(lái)模擬用戶(hù)購(gòu)買(mǎi)商品
時(shí)由于用戶(hù)余額不足導(dǎo)致本次訂單提交失敗,來(lái)驗(yàn)證下在MySQL
數(shù)據(jù)庫(kù)內(nèi)事務(wù)是否會(huì)回滾
桩皿。
免費(fèi)教程專(zhuān)題
恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專(zhuān)題
豌汇,由于文章偏多
特意添加了閱讀指南
,新文章以及之前的文章都會(huì)在專(zhuān)題內(nèi)陸續(xù)填充
泄隔,希望可以幫助大家解惑更多知識(shí)點(diǎn)拒贱。
本章文章只涉及所需要測(cè)試的服務(wù)列表
以及Seata
配置部分。
用戶(hù)提交訂單購(gòu)買(mǎi)商品大致分為以下幾個(gè)步驟:
- 減少庫(kù)存
- 扣除金額
- 提交訂單
1. 準(zhǔn)備環(huán)境
-
Seata Server
如果對(duì)
Seata Server
部署方式還不了解,請(qǐng)?jiān)L問(wèn):http://blog.yuqiyu.com/seata-init-env.html -
Eureka Server
服務(wù)注冊(cè)中心逻澳,如果對(duì)
Eureka Server
部署方式還不了解闸天,請(qǐng)?jiān)L問(wèn)http://blog.yuqiyu.com/eureka-server.html
2. 準(zhǔn)備測(cè)試服務(wù)
為了方便學(xué)習(xí)的同學(xué)查看源碼,我們本章節(jié)源碼采用Maven Module
(多模塊)的方式進(jìn)行構(gòu)建斜做。
我們用于測(cè)試的服務(wù)所使用的第三方依賴(lài)都一致苞氮,各個(gè)服務(wù)的pom.xml
文件內(nèi)容如下所示:
<dependencies>
<!--Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--openfeign接口定義-->
<dependency>
<groupId>org.minbox.chapter</groupId>
<artifactId>openfeign-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--公共依賴(lài)-->
<dependency>
<groupId>org.minbox.chapter</groupId>
<artifactId>common-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Eureka Client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.minbox.framework</groupId>
<artifactId>api-boot-starter-mybatis-enhance</artifactId>
</dependency>
</dependencies>
2.1 Openfeign接口定義模塊
由于我們服務(wù)之間采用的Openfeign
方式進(jìn)行相互調(diào)用,所以創(chuàng)建了一個(gè)模塊openfeign-service
來(lái)提供服務(wù)接口的定義
瓤逼。
- 賬戶(hù)服務(wù)提供的接口定義
賬戶(hù)服務(wù)
對(duì)外所提供的Openfeign
接口定義如下所示:
/**
* 賬戶(hù)服務(wù)接口
*
* @author 恒宇少年
*/
@FeignClient(name = "account-service")
@RequestMapping(value = "/account")
public interface AccountClient {
/**
* 扣除指定賬戶(hù)金額
*
* @param accountId 賬戶(hù)編號(hào)
* @param money 金額
*/
@PostMapping
void deduction(@RequestParam("accountId") Integer accountId, @RequestParam("money") Double money);
}
-
商品服務(wù)提供的接口定義
商品服務(wù)
對(duì)外所提供的Openfeign
接口定義如下所示:
/**
* 商品服務(wù)接口定義
*
* @author 恒宇少年
*/
@FeignClient(name = "good-service")
@RequestMapping(value = "/good")
public interface GoodClient {
/**
* 查詢(xún)商品基本信息
*
* @param goodId {@link Good#getId()}
* @return {@link Good}
*/
@GetMapping
Good findById(@RequestParam("goodId") Integer goodId);
/**
* 減少商品的庫(kù)存
*
* @param goodId {@link Good#getId()}
* @param stock 減少庫(kù)存的數(shù)量
*/
@PostMapping
void reduceStock(@RequestParam("goodId") Integer goodId, @RequestParam("stock") int stock);
}
2.2 公共模塊
公共模塊common-service
內(nèi)所提供的類(lèi)是共用的
笼吟,各個(gè)服務(wù)都可以調(diào)用,其中最為重要的是將Seata
所提供的數(shù)據(jù)源代理(DataSourceProxy
)實(shí)例化配置放到了這個(gè)模塊中霸旗,數(shù)據(jù)庫(kù)代理相關(guān)配置代碼如下所示:
/**
* Seata所需數(shù)據(jù)庫(kù)代理配置類(lèi)
*
* @author 恒宇少年
*/
@Configuration
public class DataSourceProxyAutoConfiguration {
/**
* 數(shù)據(jù)源屬性配置
* {@link DataSourceProperties}
*/
private DataSourceProperties dataSourceProperties;
public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
/**
* 配置數(shù)據(jù)源代理贷帮,用于事務(wù)回滾
*
* @return The default datasource
* @see DataSourceProxy
*/
@Primary
@Bean("dataSource")
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dataSourceProperties.getUrl());
dataSource.setUsername(dataSourceProperties.getUsername());
dataSource.setPassword(dataSourceProperties.getPassword());
dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
return new DataSourceProxy(dataSource);
}
}
該配置類(lèi)在所需要的服務(wù)中使用@Import
注解進(jìn)行導(dǎo)入使用。
2.3 賬戶(hù)服務(wù)
-
服務(wù)接口實(shí)現(xiàn)
賬戶(hù)服務(wù)
用于提供接口的服務(wù)實(shí)現(xiàn)诱告,通過(guò)實(shí)現(xiàn)openfeign-service
內(nèi)提供的AccountClient
服務(wù)定義接口來(lái)對(duì)應(yīng)提供服務(wù)實(shí)現(xiàn)撵枢,實(shí)現(xiàn)接口如下所示:
/**
* 賬戶(hù)接口實(shí)現(xiàn)
*
* @author 恒宇少年
*/
@RestController
public class AccountController implements AccountClient {
/**
* 賬戶(hù)業(yè)務(wù)邏輯
*/
@Autowired
private AccountService accountService;
@Override
public void deduction(Integer accountId, Double money) {
accountService.deduction(accountId, money);
}
}
- 服務(wù)配置(application.yml)
# 服務(wù)名
spring:
application:
name: account-service
# seata分組
cloud:
alibaba:
seata:
tx-service-group: minbox-seata
# 數(shù)據(jù)源
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# eureka
eureka:
client:
service-url:
defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/
通過(guò)spring.cloud.alibaba.seata.tx-service-group
我們可以指定服務(wù)所屬事務(wù)的分組,該配置非必填精居,默認(rèn)為spring.application.name
配置的內(nèi)容加上字符串-fescar-service-group
诲侮,如:account-service-fescar-service-group
,詳見(jiàn)com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration
配置類(lèi)源碼箱蟆。
在我本地測(cè)試環(huán)境的
Eureka Server
在10.180.98.83
服務(wù)器上沟绪,這里需要修改成你們自己的地址,數(shù)據(jù)庫(kù)連接信息也需要修改成你們自己的配置空猜。
- 導(dǎo)入Seata數(shù)據(jù)源代理配置
/**
* @author 恒宇少年
*/
@SpringBootApplication
@Import(DataSourceProxyAutoConfiguration.class)
public class AccountServiceApplication {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(AccountServiceApplication.class);
public static void main(String[] args) {
SpringApplication.run(AccountServiceApplication.class, args);
logger.info("賬戶(hù)服務(wù)啟動(dòng)成功.");
}
}
通過(guò)@Import
導(dǎo)入我們common-service
內(nèi)提供的Seata
數(shù)據(jù)源代理配置類(lèi)DataSourceProxyAutoConfiguration
绽慈。
2.4 商品服務(wù)
-
服務(wù)接口實(shí)現(xiàn)
商品服務(wù)提供商品的查詢(xún)以及庫(kù)存扣減接口服務(wù),實(shí)現(xiàn)
openfeign-service
提供的GoodClient
服務(wù)接口定義如下所示:
/**
* 商品接口定義實(shí)現(xiàn)
*
* @author 恒宇少年
*/
@RestController
public class GoodController implements GoodClient {
/**
* 商品業(yè)務(wù)邏輯
*/
@Autowired
private GoodService goodService;
/**
* 查詢(xún)商品信息
*
* @param goodId {@link Good#getId()}
* @return
*/
@Override
public Good findById(Integer goodId) {
return goodService.findById(goodId);
}
/**
* 扣減商品庫(kù)存
*
* @param goodId {@link Good#getId()}
* @param stock 減少庫(kù)存的數(shù)量
*/
@Override
public void reduceStock(Integer goodId, int stock) {
goodService.reduceStock(goodId, stock);
}
}
- 服務(wù)配置(application.yml)
spring:
application:
name: good-service
cloud:
alibaba:
seata:
tx-service-group: minbox-seata
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
service-url:
defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/
server:
port: 8081
- 導(dǎo)入Seata數(shù)據(jù)源代理配置
/**
* @author 恒宇少年
*/
@SpringBootApplication
@Import(DataSourceProxyAutoConfiguration.class)
public class GoodServiceApplication {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(GoodServiceApplication.class);
public static void main(String[] args) {
SpringApplication.run(GoodServiceApplication.class, args);
logger.info("商品服務(wù)啟動(dòng)成功.");
}
}
2.5 訂單服務(wù)
-
服務(wù)接口
訂單服務(wù)
提供了下單的接口辈毯,通過(guò)調(diào)用該接口完成下單功能坝疼,下單接口會(huì)通過(guò)Openfeign
調(diào)用account-service
、good-service
所提供的服務(wù)接口來(lái)完成數(shù)據(jù)驗(yàn)證谆沃,如下所示:
/**
* @author 恒宇少年
*/
@RestController
@RequestMapping(value = "/order")
public class OrderController {
/**
* 賬戶(hù)服務(wù)接口
*/
@Autowired
private AccountClient accountClient;
/**
* 商品服務(wù)接口
*/
@Autowired
private GoodClient goodClient;
/**
* 訂單業(yè)務(wù)邏輯
*/
@Autowired
private OrderService orderService;
/**
* 通過(guò){@link GoodClient#reduceStock(Integer, int)}方法減少商品的庫(kù)存钝凶,判斷庫(kù)存剩余數(shù)量
* 通過(guò){@link AccountClient#deduction(Integer, Double)}方法扣除商品所需要的金額,金額不足由account-service拋出異常
*
* @param goodId {@link Good#getId()}
* @param accountId {@link Account#getId()}
* @param buyCount 購(gòu)買(mǎi)數(shù)量
* @return
*/
@PostMapping
@GlobalTransactional
public String submitOrder(
@RequestParam("goodId") Integer goodId,
@RequestParam("accountId") Integer accountId,
@RequestParam("buyCount") int buyCount) {
Good good = goodClient.findById(goodId);
Double orderPrice = buyCount * good.getPrice();
goodClient.reduceStock(goodId, buyCount);
accountClient.deduction(accountId, orderPrice);
Order order = toOrder(goodId, accountId, orderPrice);
orderService.addOrder(order);
return "下單成功.";
}
private Order toOrder(Integer goodId, Integer accountId, Double orderPrice) {
Order order = new Order();
order.setGoodId(goodId);
order.setAccountId(accountId);
order.setPrice(orderPrice);
return order;
}
}
- 服務(wù)配置(application.yml)
spring:
application:
name: order-service
cloud:
alibaba:
seata:
tx-service-group: minbox-seata
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
service-url:
defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/
server:
port: 8082
- 啟用Openfeign & 導(dǎo)入Seata數(shù)據(jù)源代理配置
/**
* @author 恒宇少年
*/
@SpringBootApplication
@EnableFeignClients(basePackages = "org.minbox.chapter.seata.openfeign")
@Import(DataSourceProxyAutoConfiguration.class)
public class OrderServiceApplication {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(OrderServiceApplication.class);
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
logger.info("訂單服務(wù)啟動(dòng)成功.");
}
}
我們僅在order-service
調(diào)用了其他服務(wù)的Openfeign
接口,所以我們只需要在order-service
內(nèi)通過(guò)@EnableFeignClients
注解啟用Openfeign
接口實(shí)現(xiàn)代理尔邓。
3. 服務(wù)連接Seata Server
服務(wù)想要連接到Seata Server
需要添加兩個(gè)配置文件字旭,分別是registry.conf
、file.conf
哟沫。
-
registry.conf
注冊(cè)到
Seata Server
的配置文件,里面包含了注冊(cè)方式锌介、配置文件讀取方式嗜诀,內(nèi)容如下所示:
registry {
# file猾警、nacos、eureka隆敢、redis发皿、zk、consul
type = "file"
file {
name = "file.conf"
}
}
config {
type = "file"
file {
name = "file.conf"
}
}
-
file.conf
該配置文件內(nèi)包含了使用
file
方式連接到Eureka Server
的配置信息以及存儲(chǔ)分布式事務(wù)信息
的方式拂蝎,如下所示:
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
}
## transaction log store
store {
## store mode: file雳窟、db
mode = "file"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store
db {
datasource = "druid"
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://10.180.98.83:3306/iot-transactional"
user = "dev"
password = "dev2019."
}
}
service {
vgroup_mapping.minbox-seata = "default"
default.grouplist = "10.180.98.83:8091"
enableDegrade = false
disable = false
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
}
配置文件內(nèi)service
部分需要注意,我們?cè)?code>application.yml配置文件內(nèi)配置了事務(wù)分組為minbox-seata
匣屡,在這里需要進(jìn)行對(duì)應(yīng)配置vgroup_mapping.minbox-seata = "default"
,通過(guò)default.grouplist = "10.180.98.83:8091"
配置Seata Server
的服務(wù)列表封救。
將上面兩個(gè)配置文件在各個(gè)服務(wù)
resources
目錄下創(chuàng)建。
4. 編寫(xiě)下單邏輯
在前面說(shuō)了那么多捣作,只是做了準(zhǔn)備工作誉结,我們要為每個(gè)參與下單的服務(wù)添加對(duì)應(yīng)的業(yè)務(wù)邏輯。
-
賬戶(hù)服務(wù)
在
account-service
內(nèi)添加賬戶(hù)余額扣除業(yè)務(wù)邏輯類(lèi)券躁,AccountService
如下所示:
/**
* 賬戶(hù)業(yè)務(wù)邏輯處理
*
* @author 恒宇少年
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class AccountService {
@Autowired
private EnhanceMapper<Account, Integer> mapper;
/**
* {@link EnhanceMapper} 具體使用查看ApiBoot官網(wǎng)文檔http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html
*
* @param accountId {@link Account#getId()}
* @param money 扣除的金額
*/
public void deduction(Integer accountId, Double money) {
Account account = mapper.selectOne(accountId);
if (ObjectUtils.isEmpty(account)) {
throw new RuntimeException("賬戶(hù):" + accountId + "惩坑,不存在.");
}
if (account.getMoney() - money < 0) {
throw new RuntimeException("賬戶(hù):" + accountId + ",余額不足.");
}
account.setMoney(account.getMoney().doubleValue() - money);
mapper.update(account);
}
}
-
商品服務(wù)
在
good-service
內(nèi)添加查詢(xún)商品也拜、扣減商品庫(kù)存的邏輯類(lèi)以舒,GoodService
如下所示:
/**
* 商品業(yè)務(wù)邏輯實(shí)現(xiàn)
*
* @author 恒宇少年
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class GoodService {
@Autowired
private EnhanceMapper<Good, Integer> mapper;
/**
* 查詢(xún)商品詳情
*
* @param goodId {@link Good#getId()}
* @return {@link Good}
*/
public Good findById(Integer goodId) {
return mapper.selectOne(goodId);
}
/**
* {@link EnhanceMapper} 具體使用查看ApiBoot官網(wǎng)文檔http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html
* 扣除商品庫(kù)存
*
* @param goodId {@link Good#getId()}
* @param stock 扣除的庫(kù)存數(shù)量
*/
public void reduceStock(Integer goodId, int stock) {
Good good = mapper.selectOne(goodId);
if (ObjectUtils.isEmpty(good)) {
throw new RuntimeException("商品:" + goodId + ",不存在.");
}
if (good.getStock() - stock < 0) {
throw new RuntimeException("商品:" + goodId + "庫(kù)存不足.");
}
good.setStock(good.getStock() - stock);
mapper.update(good);
}
}
5. 提交訂單測(cè)試
我們?cè)趫?zhí)行測(cè)試之前在數(shù)據(jù)庫(kù)內(nèi)的seata_account
、seata_good
表內(nèi)對(duì)應(yīng)添加兩條測(cè)試數(shù)據(jù)慢哈,如下所示:
-- seata_good
INSERT INTO `seata_good` VALUES (1,'華為Meta 30',10,5000.00);
-- seata_account
INSERT INTO `seata_account` VALUES (1,10000.00,'2019-10-11 02:37:35',NULL);
5.1 啟動(dòng)服務(wù)
將我們本章所使用good-server
蔓钟、order-service
、account-service
三個(gè)服務(wù)啟動(dòng)卵贱。
5.2 測(cè)試點(diǎn):正常購(gòu)買(mǎi)
我們添加的賬戶(hù)余額測(cè)試數(shù)據(jù)夠我們購(gòu)買(mǎi)兩件商品滥沫,我們先來(lái)購(gòu)買(mǎi)一件商品驗(yàn)證下接口訪(fǎng)問(wèn)是否成功,通過(guò)如下命令訪(fǎng)問(wèn)下單接口:
~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=1
下單成功.
通過(guò)我們?cè)L問(wèn)/order
下單接口键俱,根據(jù)響應(yīng)的內(nèi)容我們確定商品已經(jīng)購(gòu)買(mǎi)成功兰绣。
通過(guò)查看order-service
控制臺(tái)內(nèi)容:
2019-10-11 16:52:15.477 INFO 13142 --- [nio-8082-exec-4] i.seata.tm.api.DefaultGlobalTransaction : [10.180.98.83:8091:2024417333] commit status:Committed
2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=10.180.98.83:8091:2024417333,branchId=2024417341,branchType=AT,resourceId=jdbc:mysql://localhost:3306/test,applicationData=null
2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch committing: 10.180.98.83:8091:2024417333 2024417341 jdbc:mysql://localhost:3306/test null
2019-10-11 16:52:16.412 INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
我們可以看到本次事務(wù)已經(jīng)成功Committed
。
再去驗(yàn)證下數(shù)據(jù)庫(kù)內(nèi)的賬戶(hù)余額
编振、商品庫(kù)存
是否有所扣減缀辩。
5.3 測(cè)試點(diǎn):庫(kù)存不足
測(cè)試商品添加了10
個(gè)庫(kù)存,在之前測(cè)試已經(jīng)銷(xiāo)售掉了一件商品踪央,我們測(cè)試購(gòu)買(mǎi)數(shù)量超過(guò)庫(kù)存數(shù)量時(shí)臀玄,是否有回滾日志,執(zhí)行如下命令:
~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=10
{"timestamp":"2019-10-11T08:57:13.775+0000","status":500,"error":"Internal Server Error","message":"status 500 reading GoodClient#reduceStock(Integer,int)","path":"/order"}
在我們good-service
服務(wù)控制臺(tái)已經(jīng)打印了商品庫(kù)存不足的異常信息:
java.lang.RuntimeException: 商品:1庫(kù)存不足.
at org.minbox.chapter.seata.service.GoodService.reduceStock(GoodService.java:42) ~[classes/:na]
....
我們?cè)倏?code>order-service的控制臺(tái)打印日志:
Begin new global transaction [10.180.98.83:8091:2024417350]
2019-10-11 16:57:13.771 INFO 13142 --- [nio-8082-exec-5] i.seata.tm.api.DefaultGlobalTransaction : [10.180.98.83:8091:2024417350] rollback status:Rollbacked
通過(guò)日志可以查看本次事務(wù)進(jìn)行了回滾
杯瞻。
由于庫(kù)存的驗(yàn)證在賬戶(hù)余額扣減之前镐牺,所以我們本次并不能從數(shù)據(jù)庫(kù)的數(shù)據(jù)來(lái)判斷事務(wù)是真的回滾。
5.4 測(cè)試點(diǎn):余額不足
既然商品庫(kù)存不足我們不能直接驗(yàn)證數(shù)據(jù)庫(kù)事務(wù)回滾魁莉,我們從賬戶(hù)余額不足來(lái)下手,在之前成功購(gòu)買(mǎi)了一件商品,賬戶(hù)的余額還夠購(gòu)買(mǎi)一件商品旗唁,商品庫(kù)存目前是9件
畦浓,我們本次測(cè)試購(gòu)買(mǎi)5件
商品,這樣就會(huì)出現(xiàn)購(gòu)買(mǎi)商品庫(kù)存充足
而余額不足
的應(yīng)用場(chǎng)景检疫,執(zhí)行如下命令發(fā)起請(qǐng)求:
~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=5
{"timestamp":"2019-10-11T09:03:00.794+0000","status":500,"error":"Internal Server Error","message":"status 500 reading AccountClient#deduction(Integer,Double)","path":"/order"}
我們通過(guò)查看account-service
控制臺(tái)日志可以看到:
java.lang.RuntimeException: 賬戶(hù):1讶请,余額不足.
at org.minbox.chapter.seata.service.AccountService.deduction(AccountService.java:33) ~[classes/:na]
已經(jīng)拋出了余額不足
的異常。
通過(guò)查看good-service
屎媳、order-serivce
控制臺(tái)日志夺溢,可以看到事務(wù)進(jìn)行了回滾操作。
接下來(lái)查看seata_account
表數(shù)據(jù)烛谊,我們發(fā)現(xiàn)賬戶(hù)余額沒(méi)有改變风响,賬戶(hù)服務(wù)的事務(wù)回滾
驗(yàn)證成功。
查看seata_good
表數(shù)據(jù)丹禀,我們發(fā)現(xiàn)商品的庫(kù)存也沒(méi)有改變状勤,商品服務(wù)的事務(wù)回滾
驗(yàn)證成功。
6. 總結(jié)
本章主要來(lái)驗(yàn)證分布式事務(wù)框架Seata
在MySQL
下提交與回滾有效性双泪,是否能夠完成我們預(yù)期的效果持搜,Seata
作為SpringCloud Alibaba
的核心框架,更新頻率比較高焙矛,快速的解決使用過(guò)程中遇到的問(wèn)題葫盼,是一個(gè)潛力股,不錯(cuò)的選擇村斟。
由于本章設(shè)計(jì)的代碼比較多剪返,請(qǐng)結(jié)合源碼進(jìn)行學(xué)習(xí)。
7. 本章源碼
請(qǐng)?jiān)L問(wèn)<a target="_blank">https://gitee.com/hengboy/spring-cloud-chapter</a>查看本章源碼邓梅,建議使用git clone https://gitee.com/hengboy/spring-cloud-chapter.git
將源碼下載到本地脱盲。