Seata 案例
我們會在項目中建立三個服務,倉儲服務,訂單服務,帳戶服務。當使用者下單時,會在訂單服務中建立一個訂單,然後通過遠端調用倉儲服務來扣減下單商品的庫存,再通過遠端調用賬戶服務來扣減使用者賬戶裡面的餘額。該操作跨越三個資料庫,有兩次遠端調用,很明顯會有分布式事務問題。
架構圖
1、我們需要新增三個資料庫 seata_order、seata_account、seata_store 。
2、在三個庫中建立表:
在seata_order庫中建立order 表::
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`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,
`status` INT(1) DEFAULT NULL COMMENT '訂單狀态:0:建立中;1:已完結' ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在seata_account庫中建立account表:
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`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;
INSERT INTO `seata_account`.`account`(`id`, `user_id`, `money`) VALUES (1, '1', 1000);
在seata_store庫中新增 storage表
DROP TABLE IF EXISTS `storage`;
CREATE TABLE `storage` (
`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;
INSERT INTO `seata_store`.`storage`(`id`, `commodity_code`, `count`) VALUES (1, '1', 100);
因為SEATA AT 模式需要 UNDO_LOG 表,這個需要在每一個業務庫中建立表:
DROP TABLE IF EXISTS `undo_log`;
-- 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`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
建立Module pcloud-alibaba-seata-order-service9001
修改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>pcloud-alibaba</artifactId>
<groupId>com.younger.pcloud.alibaba</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pcloud-alibaba-seata-order-service9001</artifactId>
<name>pcloud-alibaba-seata-order-service9001</name>
<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>1.4.2</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-actuator</artifactId>
</dependency>
<!--mysql-druid-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</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: 9001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定義事務組名稱需要與seata-server中的對應
tx-service-group: my_test_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: mysql
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
目錄結構:
新增registry.conf檔案:
registry.conf:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
password = ""
timeout = "0"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
custom {
name = ""
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
dataId = "seata.properties"
}
consul {
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
nodePath = "/seata/seata.properties"
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
custom {
name = ""
}
}
這個registry.conf配置檔案可以拷貝,我們上一節配置的registry.conf檔案。
新增啟動類: SeataOrderMain9001
package com.younger.pcloud.alibaba;
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.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableAutoDataSourceProxy
public class SeataOrderMain9001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMain9001.class, args);
}
}
新增domain包:
新增order類:
package com.younger.pcloud.alibaba.domain;
public class Order {
private Long id;
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
/**
* 訂單狀态:0:建立中;1:已完結
*/
private Integer status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getCommodityCode() {
return commodityCode;
}
public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
新增 Result類:
package com.younger.pcloud.alibaba.domain;
public class Result<T> {
private Integer code;
private String message;
private T data;
public Result() {
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Result(Integer code, String message) {
this(code,message,null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
新增config 包:
新增 MyBatisConfig類:
package com.younger.pcloud.alibaba.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.younger.pcloud.alibaba.dao"})
public class MyBatisConfig {
}
新增dao 包:
新增 OrderDao 類:
package com.younger.pcloud.alibaba.dao;
import com.younger.pcloud.alibaba.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface OrderDao {
/**
* 建立訂單
* @param order
*/
void createOrder(Order order);
/**
* 修改訂單狀态
*/
void update(@Param("userId") String userId, @Param("status") Integer status);
}
新增 mapper 包:
新增 OrderMapper.xml檔案:
<?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.younger.pcloud.alibaba.dao.OrderDao">
<insert id="createOrder">
INSERT INTO `order` (`id`, `user_id`, `commodity_code`, `count`, `money`,`status`)
VALUES (NULL, #{userId}, #{commodityCode}, #{count}, #{money},0);
</insert>
<update id="update">
UPDATE `order`
SET status = 1
WHERE user_id = #{userId} AND status = #{status};
</update>
</mapper>
新增service包:
新增 OrderService類:
package com.younger.pcloud.alibaba.service;
import com.younger.pcloud.alibaba.domain.Order;
public interface OrderService {
/**
* 建立訂單
*/
void createOrder(Order order);
}
新增fegin遠端調用包:
新增 AccountService類:
package com.younger.pcloud.alibaba.service.fegin;
import com.younger.pcloud.alibaba.domain.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "seata-account-service")
public interface AccountService {
/**
* 扣減賬戶餘額
* @param userId
* @param money
* @return
*/
@PostMapping("/account/decrease")
Result decrease(@RequestParam("userId") String userId, @RequestParam("money") Integer money);
}
新增StorageService類:
package com.younger.pcloud.alibaba.service.fegin;
import com.younger.pcloud.alibaba.domain.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "seata-store-service")
public interface StorageService {
/**
* 扣減庫存
*/
@PostMapping(value = "/storage/decrease")
Result decrease(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}
新增OrderService 實作類OrderServiceImpl :
package com.younger.pcloud.alibaba.service.impl;
import com.younger.pcloud.alibaba.dao.OrderDao;
import com.younger.pcloud.alibaba.domain.Order;
import com.younger.pcloud.alibaba.service.OrderService;
import com.younger.pcloud.alibaba.service.fegin.AccountService;
import com.younger.pcloud.alibaba.service.fegin.StorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
@Override
public void createOrder(Order order) {
log.info("開始建立訂單....");
//本應用建立訂單
orderDao.createOrder(order);
//遠端調用庫存服務扣減庫存
log.info("order-service------扣減庫存開始");
storageService.decrease(order.getCommodityCode(),order.getCount());
log.info("order-service------扣減庫存結束");
//遠端調用賬戶服務扣減餘額
log.info("order-service-------中扣減使用者餘額開始");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("order-service-------中扣減餘額結束");
//修改訂單狀态為已完成
log.info("order-service-------中修改訂單狀态開始");
orderDao.update(order.getUserId(),0);
log.info("order-service-------中修改訂單狀态結束");
log.info("訂單完成!!!!!");
}
}
新增Controller 包:
新增 OrderController 類:
package com.younger.pcloud.alibaba.controller;
import com.younger.pcloud.alibaba.domain.Order;
import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.OrderService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@PostMapping("/order/create")
public Result createOrder(@RequestBody Order order) {
orderService.createOrder(order);
return new Result(200,"訂單建立成功");
}
}
建立modul pcloud-alibaba-seata-store-service9002
目錄結構:
修改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>pcloud-alibaba</artifactId>
<groupId>com.younger.pcloud.alibaba</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pcloud-alibaba-seata-store-service9002</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>1.4.2</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>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</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: 9002
spring:
application:
name: seata-store-service
cloud:
alibaba:
seata:
#自定義事務組名稱需要與seata-server中的對應
tx-service-group: my_test_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_store
username: root
password: mysql
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
拷貝 file.conf 和 registry.conf 檔案到Resource目錄下:
新增啟動類 SeataStorageServiceMain9002:
package com.younger.pcloud.alibaba;
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.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableAutoDataSourceProxy
public class SeataStorageServiceMain9002 {
public static void main(String[] args) {
SpringApplication.run(SeataStorageServiceMain9002.class ,args);
}
}
從9001 拷貝 config 目錄和registry.conf 到 9002
新增domain 目錄:
新增Result 類:
package com.younger.pcloud.alibaba.domain;
public class Result<T> {
private Integer code;
private String message;
private T data;
public Result() {
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Result(Integer code, String message) {
this(code,message,null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
新增 Store 類:
package com.younger.pcloud.alibaba.domain;
public class Store {
private Long id;
private String commodityCode;
private Integer count;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCommodityCode() {
return commodityCode;
}
public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
新增dao包:
新增 StoreDao 類:
package com.younger.pcloud.alibaba.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface StoreDao {
/**
* 扣減庫存
*/
void decrease(@Param("commodityCode") String commodityCode, @Param("count") Integer count);
}
新增 StoreMapper.xml 檔案:
<?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.younger.pcloud.alibaba.dao.StoreDao">
<update id="decrease">
UPDATE storage
SET count = count - #{count}
WHERE commodity_code = #{commodityCode}
</update>
</mapper>
新增 service 包:
新增 StoreService類:
package com.younger.pcloud.alibaba.service;
public interface StoreService {
/**
* 扣減庫存
*/
void decrease(String commodityCode, Integer count);
}
新增 StoreService實作類 StoreServiceImpl類:
package com.younger.pcloud.alibaba.service.impl;
import com.younger.pcloud.alibaba.dao.StoreDao;
import com.younger.pcloud.alibaba.service.StoreService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class StoreServiceImpl implements StoreService {
@Resource
private StoreDao storeDao;
@Override
public void decrease(String commodityCode, Integer count) {
log.info("storage-service-------中扣減庫存開始");
storeDao.decrease(commodityCode,count);
log.info("storage-service--------中扣減庫存結束");
}
}
新增controller 包:
新增 StoreController 類:
package com.younger.pcloud.alibaba.controller;
import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.StoreService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class StoreController {
@Resource
private StoreService storeService;
/**
* 扣減庫存
*/
@RequestMapping("/storage/decrease")
public Result decrease(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count) {
storeService.decrease(commodityCode, count);
return new Result(200,"庫存扣減成功!");
}
}
建立modul pcloud-alibaba-seata-account-service9003
修改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>pcloud-alibaba</artifactId>
<groupId>com.younger.pcloud.alibaba</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pcloud-alibaba-seata-account-service9003</artifactId>
<name>pcloud-alibaba-seata-account-service9003</name>
<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>1.4.2</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>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</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: 9003
spring:
application:
name: seata-account-service
cloud:
alibaba:
seata:
#自定義事務組名稱需要與seata-server中的對應
tx-service-group: my_test_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account
username: root
password: mysql
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
新增啟動類SeataAccountServiceMain9003:
package com.younger.pcloud.alibaba;
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.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableAutoDataSourceProxy
public class SeataAccountServiceMain9003 {
public static void main(String[] args) {
SpringApplication.run(SeataAccountServiceMain9003.class,args);
}
}
目錄結構:
從9001 拷貝 config 目錄和registry.conf 到 9003
新增domain 包:
新增 Account 類:
package com.younger.pcloud.alibaba.domain;
public class Account {
private Long id;
private String userId;
private Integer money;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}
新增 Result 類:
package com.younger.pcloud.alibaba.domain;
public class Result<T> {
private Integer code;
private String message;
private T data;
public Result() {
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Result(Integer code, String message) {
this(code,message,null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
新增dao包:
新增 AccountDao 類:
package com.younger.pcloud.alibaba.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AccountDao {
/**
* 扣減賬戶餘額
*/
void decrease(@Param("userId") String userId, @Param("money") Integer money);
}
新增 AccountMapper.xml 檔案:
<?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.younger.pcloud.alibaba.dao.AccountDao">
<update id="decrease">
UPDATE account
SET
money = money - #{money}
WHERE
user_id = #{userId};
</update>
</mapper>
新增service 包:
新增 AccountService 類:
package com.younger.pcloud.alibaba.service;
import org.springframework.web.bind.annotation.RequestParam;
public interface AccountService {
/**
* 扣減賬戶餘額
* @param userId 使用者id
* @param money 金額
*/
void decrease(@RequestParam("userId") String userId, @RequestParam("money") Integer money);
}
新增 AccountService 的實作類AccountServiceImpl:
package com.younger.pcloud.alibaba.service.impl;
import com.younger.pcloud.alibaba.dao.AccountDao;
import com.younger.pcloud.alibaba.service.AccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
@Resource
AccountDao accountDao;
/**
* 扣減賬戶餘額
*/
@Override
public void decrease(String userId, Integer money) {
log.info("account-service--------中扣減賬戶餘額開始");
accountDao.decrease(userId,money);
log.info("account-service-------中扣減賬戶餘額結束");
}
}
新增 controller包:
新增 AccountController 類:
package com.younger.pcloud.alibaba.controller;
import com.younger.pcloud.alibaba.domain.Result;
import com.younger.pcloud.alibaba.service.AccountService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class AccountController {
@Resource
private AccountService accountService;
/**
* 扣減賬戶餘額
*/
@RequestMapping("/account/decrease")
public Result decrease(@RequestParam("userId") String userId, @RequestParam("money") Integer money){
accountService.decrease(userId,money);
return new Result(200,"賬戶餘額扣減成功!");
}
}
啟動項目測試:
通過日志,我們可以知道我們的事務已經注冊成功了。
建立訂單: http://localhost:9001/order/create
參數請求:
{
"id":1,
"userId":"1",
"commodityCode":"1",
"count":1,
"money":55
}
我們服務之間的調用已經成功了。
我們看下表的資料:
表裡面的資料和我們預想的結果是一樣的。
我們現在沒有在我們的業務上面添加事務,我們測試一下逾時會怎麼樣?
我們修改我們的AccountServiceImpl類:
可以看到我們在類上面添加了逾時異常。
我們重新開機下項目測試:
測試:
此刻通路已經逾時了。
我們看下資料庫:
可以看到,我們在已經建立了訂單,并且庫存已經抵扣了,但是使用者沒有支付成功。這個就是我們常見的分布式事務的問題。
我們在我們的業務類上面添加我們的分布式注解:@GlobalTransactional
我們修改 OrderServiceImpl類:
隻需要在我們的業務類上添加一個 @GlobalTransactional注解即可,這個name是我們自己定義的,但是需要全局唯一。
我們重新啟動項目看下:
測試:
執行也是逾時的,我們在看下資料庫:
我們看到訂單和庫存都沒有記錄。說明我們的分布式事務已經成功了。
總結一下分布式事務的執行流程:
1、TM 開啟分布式事務(TM 向 TC 注冊全局事務記錄)。
2、RM 向 TC 彙報資源準備狀态。
3、TM 通知 TC 送出/復原分布式事務。
4、TC 彙總事務資訊,決定分布式事務是送出還是復原。
5、TC 通知所有 RM 送出/復原 資源,事務二階段結束。