有關seata的安裝,請參看部落格:https://blog.51cto.com/lianghecai/5759100
業務需求:下單—減庫存—扣錢—改訂單狀态
當使用者下單時,會在訂單服務中建立一個訂單,然後通過遠端調用庫存服務來扣減下單商品的庫存;再通過遠端調用賬戶服務來扣減使用者賬戶裡面的餘額;最後訂單服務中修改訂單狀态。
需要涉及到三個子產品:
子產品名 | 資料庫名 | port | Context-path | Application-name |
---|---|---|---|---|
fafu-account | seata_account | 3001 | fafu | Fafu-account-3001 |
fafu-storage | seata_storage | 4001 | fafu | Fafu-storage-4001 |
fafu-order | seata_order | 5001 | fafu | Fafu-order-5001 |
可以看出:該操作需要跨域三個資料庫,有兩次遠端調用,需要用到分布式事務技術。
資料庫準備
建立seata_order資料庫,然後在其中建立表tb_order:
CREATE TABLE `tb_order` (
`id` bigint UNSIGNED NOT NULL COMMENT '訂單編号',
`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '使用者編号',
`sku_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '商品編号',
`count` int UNSIGNED NULL DEFAULT NULL COMMENT '數量',
`money` decimal(10, 3) UNSIGNED NULL DEFAULT NULL COMMENT '金額',
`state` int UNSIGNED NULL DEFAULT NULL COMMENT '狀态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
上面表的設計隻是模拟性質的,大概對應的是拼多多的業務,一個訂單中隻能購買1種商品,但這種商品可以購買多個。
建立seata_storage資料庫,然後在其中建立表tb_storage
CREATE TABLE `tb_storage` (
`id` bigint UNSIGNED NOT NULL COMMENT '編号',
`sku_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '商品編号',
`total` int UNSIGNED NULL DEFAULT NULL COMMENT '總庫存',
`used` int UNSIGNED NULL DEFAULT NULL COMMENT '已用庫存',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
建立seata_account資料庫,然後在其中建立表tb_account:
CREATE TABLE `tb_account` (
`id` bigint UNSIGNED NOT NULL COMMENT '編号',
`total` decimal(11, 3) UNSIGNED NULL DEFAULT NULL COMMENT '總額度',
`used` decimal(11, 3) UNSIGNED NULL DEFAULT NULL COMMENT '已用額度',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
依次在三個資料庫建立undo_log 表,用于儲存需要復原的資料:
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid` ASC, `branch_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
注:每個服務對應的業務資料庫都需要建立這個日志表。
項目共同部分
Nacos中
在Nacos添加給用戶端加載使用的配置seata-client-demo.yml:
- Data Id:seata-client-demo.yml
- Group:SEATA_GROUP
注意:上面配置中的group和nampespace都是在Nacos中建立命名空間時建立的。
上面配置的具體内容如下:
# seata配置
seata:
enabled: true
application-id: seata-cilent-deme
# Seata 事務組編号,此處需于 seata 相同
tx-service-group: default-tx-group
config:
type: nacos
nacos:
# nacos ip位址
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 99970600-6f67-43fd-a2a2-4e9795947bf4
data-id: seata-server.properties
registry:
type: nacos
nacos:
application: seata-server
# nacos ip位址
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 99970600-6f67-43fd-a2a2-4e9795947bf4
依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</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-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- nacos自從2020版本之後不再整合的是Netflix,也就沒有ribbon了 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel配置持久化到Nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<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>
Result
@Getter
@ToString
public class Result<T> {
/**
* 請求響應狀态碼
*/
private int code;
/**
* 請求結果描述資訊
*/
private String msg;
/**
* 請求結果資料
*/
private T data;
public Result<T> setCode(int code) {
this.code = code;
return this;
}
public Result<T> setMsg(String msg) {
this.msg = msg;
return this;
}
public Result<T> setData(T data) {
this.data = data;
return this;
}
public static void main(String[] args) {
Result<Object> result = new Result<>().addData("a", "aaa", "b", 123, "c", new Date());
System.out.println(result);
}
/**
* 将key-value形式的成對出現的參數轉換為JSON
*
* @param objs
* @return
*/
public Result<T> addData(Object... objs) {
if (objs.length % 2 != 0) {
throw new RuntimeException("參數個數不對");
}
for (int i = 0; i < objs.length; i += 2) {
if (!(objs[i] instanceof String)) {
throw new RuntimeException("奇數參數必須為字元串");
}
}
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < objs.length; i += 2) {
map.put((String) objs[i], objs[i + 1]);
}
this.data = (T) map;
return this;
}
}
ResultUtil
public class ResultUtil {
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("成功");
return result;
}
public static <T> Result<T> success(Integer code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("成功");
result.setData(data);
return result;
}
public static <T> Result<T> error() {
Result<T> result = new Result<>();
result.setCode(500);
result.setMsg("失敗");
return result;
}
public static <T> Result<T> error(Integer code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
public static void main(String[] args) {
Result error = ResultUtil.error().addData("a", "aaaa", "b", LocalDateTime.now(), "c", true);
System.out.println(error);
//ResultBean res = ResultBeanUtil.success(DataUtil.build(UserVO.class));
//System.out.println(res);
}
}
druid資料源代理配置
- MyBatis版本
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
return factoryBean.getObject();
}
}
- MyBatisPlus版本
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
在2.2.0.RELEASE及以後,資料源代理自動實作了,不需要再手動去配置一個代理類。
seata-account-3001
第一步;建立項目,添加依賴,修改application.yml檔案:
server:
port: 3001
servlet:
context-path: /seata
spring:
application:
name: seata-account-3001
#配置資料源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata_account?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
# 連接配接池的配置資訊:初始化大小,最小,最大
initial-size: 8
min-idle: 1
max-active: 20
# 配置擷取連接配接等待逾時的時間
max-wait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
time-between-eviction-runs-millis: 60000
# 配置一個連接配接在池中最小生存的時間,機關是毫秒
min-evictable-idle-time-millis: 300000
#驗證庫是否正常sql
validation-query: select 'x' from dual
#空閑時驗證,防止連接配接斷開
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打開PSCache,并且指定每個連接配接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用于防火牆
filters: stat,wall,slf4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多個DruidDataSource的監控資料
use-global-data-source-stat: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos位址
sentinel:
transport:
# 配置sentinel dashboard位址
dashboard: 127.0.0.1:8080
# 預設端口8719,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
port: 8719
config:
import:
- optional:nacos:seata-client-demo.yml
seata:
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
management:
endpoints:
web:
exposure:
include: '*' #暴露出所有的端點
#MyBatisPlus相關配置
mybatis-plus:
#mapper映射檔案位置,多個目錄用逗号或者分号分隔(告訴 Mapper 所對應的 XML 檔案位置)
mapper-locations: classpath:mapper/*.xml
#實體掃描,多個package用逗号或者分号分隔
typeAliasesPackage: com.seata.account.domain
# 以下配置均有預設值,可以不設定
global-config:
db-config:
#主鍵類型
id-type: auto
table-underline: true
#資料庫大寫下劃線轉換
capital-mode: true
configuration:
# 是否開啟自動駝峰命名規則映射:從資料庫列名到Java屬性駝峰命名的類似映射
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull
jdbc-type-for-null: 'null'
# 如果查詢結果中包含空值的列,則 MyBatis 在映射的時候,不會映射這個字段
call-setters-on-nulls: true
# 将執行的sql列印出來,在開發或測試的時候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第二步:項目主啟動類:
@EnableDiscoveryClient
@SpringBootApplication
public class SeataAccountApplication {
public static void main(String[] args) {
SpringApplication.run(SeataAccountApplication.class, args);
}
}
第三步:利用MyBatisCodeHelperPro生成Account表對應的實體類、Mapper、Service代碼。
第四步:控制器AccountController
@RestController
@RequestMapping("/account")
public class AccountController {
@Resource
private AccountService accountService;
@PostMapping("/decrease")
Result decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {
//模拟異常,全局事務復原
// System.out.println(3/0); //--------------------①
accountService.update(Wrappers.<Account>lambdaUpdate()
.eq(Account::getId, userId)
.setSql("used = used + " + money)
);
return ResultUtil.success().setMsg("扣款成功");
}
}
啟動項目,在Nacos中會看到:
post方式請求:http://localhost:3001/seata/account/decrease?userId=1&money=15
能夠正常通路。
seata-storage-4001
第一步;建立項目,添加依賴,修改application.yml檔案:
server:
port: 4001
servlet:
context-path: /seata
spring:
application:
name: seata-storage-4001
#配置資料源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata_storage?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
# 連接配接池的配置資訊:初始化大小,最小,最大
initial-size: 8
min-idle: 1
max-active: 20
# 配置擷取連接配接等待逾時的時間
max-wait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
time-between-eviction-runs-millis: 60000
# 配置一個連接配接在池中最小生存的時間,機關是毫秒
min-evictable-idle-time-millis: 300000
#驗證庫是否正常sql
validation-query: select 'x' from dual
#空閑時驗證,防止連接配接斷開
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打開PSCache,并且指定每個連接配接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用于防火牆
filters: stat,wall,slf4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多個DruidDataSource的監控資料
use-global-data-source-stat: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos位址
sentinel:
transport:
# 配置sentinel dashboard位址
dashboard: 127.0.0.1:8080
# 預設端口8719,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
port: 8719
config:
import:
- optional:nacos:seata-client-demo.yml
seata:
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
management:
endpoints:
web:
exposure:
include: '*' #暴露出所有的端點
#MyBatisPlus相關配置
mybatis-plus:
#mapper映射檔案位置,多個目錄用逗号或者分号分隔(告訴 Mapper 所對應的 XML 檔案位置)
mapper-locations: classpath:mapper/*.xml
#實體掃描,多個package用逗号或者分号分隔
typeAliasesPackage: com.seata.storage.domain
# 以下配置均有預設值,可以不設定
global-config:
db-config:
#主鍵類型
id-type: auto
table-underline: true
#資料庫大寫下劃線轉換
capital-mode: true
configuration:
# 是否開啟自動駝峰命名規則映射:從資料庫列名到Java屬性駝峰命名的類似映射
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull
jdbc-type-for-null: 'null'
# 如果查詢結果中包含空值的列,則 MyBatis 在映射的時候,不會映射這個字段
call-setters-on-nulls: true
# 将執行的sql列印出來,在開發或測試的時候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第二步:項目主啟動類添加注解
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消資料源自動建立
public class SeataStorageApplication {
public static void main(String[] args) {
SpringApplication.run(SeataStorageApplication.class, args);
}
}
第三步:利用MyBatisCodeHelperPro生成Storage表對應的實體類、Mapper、Service代碼。
第四步:控制器StorageController
@RestController
@RequestMapping("/storage")
public class StorageController {
@Resource
private StorageService storageService;
@PostMapping("/decrease")
Result decrease(@RequestParam("skuId") Long skuId, @RequestParam("count") Integer count) {
storageService.update(Wrappers.<Storage>lambdaUpdate()
.eq(Storage::getSkuId, skuId)
.setSql("used= used+" + count)
);
return ResultUtil.success().setMsg("庫存扣減成功");
}
}
啟動項目,在Nacos中會看到:
post方式請求:http://localhost:4001/seata/storage/decrease?skuId=1&money=3
能夠正常通路。
seata-order-5001
第一步;建立項目,添加依賴,修改application.yml檔案:
server:
port: 5001
servlet:
context-path: /seata
spring:
application:
name: seata-order-5001
#配置資料源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
# 連接配接池的配置資訊:初始化大小,最小,最大
initial-size: 8
min-idle: 1
max-active: 20
# 配置擷取連接配接等待逾時的時間
max-wait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
time-between-eviction-runs-millis: 60000
# 配置一個連接配接在池中最小生存的時間,機關是毫秒
min-evictable-idle-time-millis: 300000
#驗證庫是否正常sql
validation-query: select 'x' from dual
#空閑時驗證,防止連接配接斷開
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打開PSCache,并且指定每個連接配接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用于防火牆
filters: stat,wall,slf4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多個DruidDataSource的監控資料
use-global-data-source-stat: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos位址
sentinel:
transport:
# 配置sentinel dashboard位址
dashboard: 127.0.0.1:8080
# 預設端口8719,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
port: 8719
config:
import:
- optional:nacos:seata-client-demo.yml
seata:
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
management:
endpoints:
web:
exposure:
include: '*' #暴露出所有的端點
#MyBatisPlus相關配置
mybatis-plus:
#mapper映射檔案位置,多個目錄用逗号或者分号分隔(告訴 Mapper 所對應的 XML 檔案位置)
mapper-locations: classpath:mapper/*.xml
#實體掃描,多個package用逗号或者分号分隔
typeAliasesPackage: com.seata.order.domain
# 以下配置均有預設值,可以不設定
global-config:
db-config:
#主鍵類型
id-type: auto
table-underline: true
#資料庫大寫下劃線轉換
capital-mode: true
configuration:
# 是否開啟自動駝峰命名規則映射:從資料庫列名到Java屬性駝峰命名的類似映射
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull
jdbc-type-for-null: 'null'
# 如果查詢結果中包含空值的列,則 MyBatis 在映射的時候,不會映射這個字段
call-setters-on-nulls: true
# 将執行的sql列印出來,在開發或測試的時候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第二步:項目主啟動類上添加注解:
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消資料源自動建立
public class SeataOrderApplication {
public static void main(String[] args) {
SpringApplication.run(SeataOrderApplication.class, args);
}
}
第三步:利用MyBatisCodeHelperPro生成Order表對應的實體類、Mapper、Service代碼。
第四步:在feign包下建立:
- AccountFeignService.java
@FeignClient(name = "seata-account-3001", path = "/seata")
@RequestMapping("/account")
public interface AccountFeignService {
/**
* 将skuId對應的商品的庫存減少count
* @param userId
* @param money
* @return
*/
@PostMapping("/decrease")
Result decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
- StorageFeignService.java
@FeignClient(name = "seata-storage-4001", path = "/seata")
@RequestMapping("/storage")
public interface StorageFeignService {
/**
* 将skuId對應的商品的庫存減少count
* @param skuId
* @param count
* @return
*/
@PostMapping("/decrease")
Result decrease(@RequestParam("skuId") Long skuId, @RequestParam("count") Integer count);
}
第五步:修改OrderServiceImpl的代碼如下所示:
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private StorageFeignService storageFeignService;
@Resource
private AccountFeignService accountFeignService;
@Override
// @GlobalTransactional(rollbackFor = Exception.class) //全局異常 //----------------②
public void create(Order order) {
System.out.println("user建訂單");
orderMapper.insert(order);
System.out.println("扣減庫存");
storageFeignService.decrease(order.getSkuId(),order.getCount());
System.out.println("賬戶餘額扣減");
accountFeignService.decrease(order.getUserId(),order.getMoney());
System.out.println("修改訂單的狀态");
//1表示完成狀态
orderMapper.updateStateByUserId(order.getUserId(),1);
System.out.println("下單結束");
}
}
第六步:控制器OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/create")
public Result create(Order order){
orderService.create(order);
return ResultUtil.success().setMsg("下單成功!");
}
}
測試
- 保持編号①和編号②處于注釋狀态:請求url,tb_order表增加了一條記錄,使用者賬戶用掉30,庫存用掉10。
- 保持編号②處于注釋狀态,去掉編号①前的注釋:請求url,tb_order表增加了一條記錄但狀态值為null,使用者賬戶沒有發生變化,庫存用掉10。沒有能夠保證資料的完整性
- 去掉編号①和編号②前的注釋:請求url,三個表的資料都沒有發生變化。