实战
1. 分布式事务业务说明
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
即:下订单-->扣库存-->减账户(余额)-->改状态
2. 构建业务数据库
-- 存储订单的数据库
CREATE DATABASE seata_order;
use seata_order;
-- 业务表t_order
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;
-- 存储库存的数据库
CREATE DATABASE seata_storage;
use seata_storage;
-- 业务表t_storage
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');
-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;
-- 存储账户信息的数据库
CREATE DATABASE seata_account;
use seata_account;
-- 业务表t_account
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000');
-- 回滚日志表
source /root/program/seata/conf/db_undo_log.sql;
3. 构建业务微服务
3.1 订单微服务
-
建Module【seata-order-service2001】
-
改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>cloud2020</artifactId><groupId>com.atguigu.springcloud</groupId><version>1.0-SNAPSHOT</version></parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service2001</artifactId>
<dependencies> <!--nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--seata:为了保证版本一致,剔除了自带的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> <!-- 版本要和我们安装的Seata-Server版本一致 --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web-actuator--> <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>
-
写YML
server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应 tx-service-group: fsp_tx_group nacos: discovery: server-addr: 192.168.59.128:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.59.128:3306/seata_order username: root password: rootroot feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
-
在resources下创建file.conf
从Seata安装目录下的conf下拿过来(file.conf的原始文件,即备份的文件),做如下修改
-
在resources下创建registry.conf
从Seata安装目录下的conf下拿过来(将安装时修改后的文件拿过来,改下ip即可)
-
创建domain【实体类】
-
CommonResult
package com.atguigu.springcloud.alibaba.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message){ this(code,message,null); } }
-
Order
我这里是通过mybatis generater插件生成的,如果连接数据库时数据库不允许远程访问,执行
set global validate_password_policy=LOW; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'rootroot' WITH GRANT OPTION; FLUSH PRIVILEGES;
package com.atguigu.springcloud.alibaba.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Order { private Long id; /** * 用户id */ private Long userId; /** * 产品id */ private Long productId; /** * 数量 */ private Integer count; /** * 金额 */ private Long money; /** * 订单状态:0:创建中; 1:已完结 */ private Integer status; }
-
-
Dao接口及实现
-
OrderDao
package com.atguigu.springcloud.alibaba.dao; import com.atguigu.springcloud.alibaba.domain.Order; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface OrderDao { //新建订单 void create(Order order); //修改订单状态,从零改为1 void update(@Param("userId") Long userId,@Param("status") Integer status); }
-
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.atguigu.springcloud.alibaba.domain.OrderMapper"> <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Order"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="count" property="count" jdbcType="INTEGER"/> <result column="money" property="money" jdbcType="DECIMAL"/> <result column="status" property="status" jdbcType="INTEGER"/> </resultMap> <insert id="create"> insert into t_order (id,user_id,product_id,count,money,status) values (null,#{userId},#{productId},#{count},#{money},0); </insert>
```<update id="update"> update t_order set status = 1 where user_id=#{userId} and status = #{status}; </update>
-
-
Service接口及实现
-
OrderService
package com.atguigu.springcloud.alibaba.service; import com.atguigu.springcloud.alibaba.domain.Order;
public interface OrderService{
void create(Order order);
}
``` -
StorageService
package com.atguigu.springcloud.alibaba.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient("seata-storage-service") public interface StorageService { @PostMapping("/storage/decrease") void decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count); }
-
AccountService
package com.atguigu.springcloud.alibaba.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; @FeignClient("seata-account-service") public interface AccountService { @PostMapping("/account/decrease") void decrease(@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money); }
-
OrderServiceImpl
package com.atguigu.springcloud.alibaba.service.impl; import com.atguigu.springcloud.alibaba.dao.OrderDao; import com.atguigu.springcloud.alibaba.domain.Order; import com.atguigu.springcloud.alibaba.service.AccountService; import com.atguigu.springcloud.alibaba.service.OrderService; import com.atguigu.springcloud.alibaba.service.StorageService; import io.seata.spring.annotation.GlobalTransactional; 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 create(Order order){ log.info("----->开始新建订单"); //新建订单 orderDao.create(order); //扣减库存 log.info("----->订单微服务开始调用库存,做扣减Count"); storageService.decrease(order.getProductId(),order.getCount()); log.info("----->订单微服务开始调用库存,做扣减end"); //扣减账户 log.info("----->订单微服务开始调用账户,做扣减Money"); accountService.decrease(order.getUserId(),order.getMoney()); log.info("----->订单微服务开始调用账户,做扣减end"); //修改订单状态,从零到1代表已经完成 log.info("----->修改订单状态开始"); orderDao.update(order.getUserId(),0); log.info("----->修改订单状态结束"); log.info("----->下订单结束了"); }
}
```
-
-
Controller
package com.atguigu.springcloud.alibaba.controller; import com.atguigu.springcloud.alibaba.domain.CommonResult; import com.atguigu.springcloud.alibaba.domain.Order; import com.atguigu.springcloud.alibaba.service.OrderService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource;
@RestController
public class OrderController{
@Resource
private OrderService orderService;@GetMapping("/order/create") public CommonResult create(Order order) { orderService.create(order); return new CommonResult(200,"订单创建成功"); }
}
-
配置类
-
MyBatisConfig
package com.atguigu.springcloud.alibaba.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan({"com.atguigu.springcloud.alibaba.dao"}) public class MyBatisConfig { }
- DataSourceProxyConfig ```java package com.atguigu.springcloud.alibaba.config; import com.alibaba.druid.pool.DruidDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {@Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); }
}
``` -
-
主启动
package com.atguigu.springcloud.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)//取消数据源自动创建的配置 public class SeataOrderMainApp2001{ public static void main(String[] args) { SpringApplication.run(SeataOrderMainApp2001.class, args); } }
3.2 库存微服务
-
建Module【seata-order-service2002】
-
改POM
同4.4.3.1
-
写YML
server: port: 2002 spring: application: name: seata-storage-service cloud: alibaba: seata: #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应 tx-service-group: fsp_tx_group nacos: discovery: server-addr: 192.168.59.128:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.59.128:3306/seata_storage username: root password: rootroot feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
-
在resources下创建file.conf 、registry.conf
同4.4.3.1
-
创建domain【实体类】
-
CommonResult
同4.4.3.1
-
Storage
package com.atguigu.springcloud.alibaba.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Storage { private Long id; /** * 产品id */ private Long productId; /** * 总库存 */ private Integer total; /** * 已用库存 */ private Integer used; /** * 剩余库存 */ private Integer residue; }
-
-
Dao接口及实现
-
StorageDao
package com.atguigu.springcloud.alibaba.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface StorageDao { //扣减库存信息 void decrease(@Param("productId") Long productId, @Param("count") Integer count); }
-
StorageMapper.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.atguigu.springcloud.alibaba.dao.StorageDao"> <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Storage"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="total" property="total" jdbcType="INTEGER"/> <result column="used" property="used" jdbcType="INTEGER"/> <result column="residue" property="residue" jdbcType="INTEGER"/> </resultMap> <update id="decrease"> UPDATE t_storage SET used = used + #{count},residue = residue - #{count} WHERE product_id = #{productId} </update> </mapper>
-
-
Service接口及实现
-
StorageService
package com.atguigu.springcloud.alibaba.service;
public interface StorageService {
// 扣减库存 void decrease(Long productId, Integer count);
}
``` -
StorageServiceImpl
package com.atguigu.springcloud.alibaba.service.impl; import com.atguigu.springcloud.alibaba.dao.StorageDao; import com.atguigu.springcloud.alibaba.service.StorageService ; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource;
@Service
public class StorageServiceImpl implements StorageService {private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class); @Resource private StorageDao storageDao; // 扣减库存 @Override public void decrease(Long productId, Integer count) { LOGGER.info("------->storage-service中扣减库存开始"); storageDao.decrease(productId,count); LOGGER.info("------->storage-service中扣减库存结束"); }
}
```
-
-
Controller
package com.atguigu.springcloud.alibaba.controller;
import com.atguigu.springcloud.alibaba.domain.CommonResult ;
import com.atguigu.springcloud.alibaba.service.StorageService ;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class StorageController {@Autowired private StorageService storageService; //扣减库存 @RequestMapping("/storage/decrease") public CommonResult decrease(Long productId, Integer count) { storageService.decrease(productId, count); return new CommonResult(200,"扣减库存成功!"); }
}
-
配置类
同4.4.3.1
-
主启动
package com.atguigu.springcloud.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; @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class SeataStorageServiceApplication2002 { public static void main(String[] args) { SpringApplication.run(SeataStorageServiceApplication2002.class, args); } }
3.3 账户微服务
-
建Module【seata-order-service2003】
-
改POM
同4.4.3.1
-
写YML
server: port: 2003 spring: application: name: seata-account-service cloud: alibaba: seata: #自定义事务组名称需要与seata-server中file.conf文件中配置的事务组名称对应 tx-service-group: fsp_tx_group nacos: discovery: server-addr: 192.168.59.128:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.59.128:3306/seata_account username: root password: rootroot feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
-
在resources下创建file.conf 、registry.conf
同4.4.3.1
-
创建domain【实体类】
-
CommonResult
同4.4.3.1
-
Account
package com.atguigu.springcloud.alibaba.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data @AllArgsConstructor @NoArgsConstructor public class Account { /** * id */ private Long id; /** * 用户id */ private Long userId; /** * 总额度 */ private BigDecimal total; /** * 已用余额 */ private BigDecimal used; /** * 剩余可用额度 */ private BigDecimal residue; }
-
-
Dao接口及实现
-
AccountDao
package com.atguigu.springcloud.alibaba.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import java.math.BigDecimal; @Mapper public interface AccountDao { /** * 扣减账户余额 */ void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money); }
-
AccountDao.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.atguigu.springcloud.alibaba.dao.AccountDao"> <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Account"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="total" property="total" jdbcType="DECIMAL"/> <result column="used" property="used" jdbcType="DECIMAL"/> <result column="residue" property="residue" jdbcType="DECIMAL"/> </resultMap> <update id="decrease"> UPDATE t_account SET residue = residue - #{money},used = used + #{money} WHERE user_id = #{userId}; </update> </mapper>
-
-
Service接口及实现
-
AccountService
package com.atguigu.springcloud.alibaba.service; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.math.BigDecimal;
public interface AccountService {
/** * 扣减账户余额 */ void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
``` -
AccountServiceImpl
package com.atguigu.springcloud.alibaba.service.impl;
import com.atguigu.springcloud.alibaba.dao.AccountDao;
import com.atguigu.springcloud.alibaba.service.AccountService ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;/**
-
账户业务实现类
*/
@Service
public class AccountServiceImpl implements AccountService {private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
AccountDao accountDao;/**
-
扣减账户余额
*/
@Override
public void decrease(Long userId, BigDecimal money) {LOGGER.info("------->account-service中扣减账户余额开始");
// try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
}
```
-
-
-
-
Controller
package com.atguigu.springcloud.alibaba.controller; import com.atguigu.springcloud.alibaba.domain.CommonResult ; import com.atguigu.springcloud.alibaba.service.AccountService ; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; 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; import java.math.BigDecimal; @RestController public class AccountController { @Resource AccountService accountService; /** * 扣减账户余额 */ @RequestMapping("/account/decrease") public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){ accountService.decrease(userId,money); return new CommonResult(200,"扣减账户余额成功!"); } }
-
配置类
同4.4.3.1
-
主启动
package com.atguigu.springcloud.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;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMainApp2003
{
public static void main(String[] args)
{
SpringApplication.run(SeataAccountMainApp2003.class, args);
}
}
4.4.4 测试
启动单机版Nacos
启动Seata
启动订单、库存、账户微服务
访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,执行成功。
修改seata-order-service2003服务的AccountServiceImpl,模拟超时异常
```java
package com.atguigu.springcloud.alibaba.service.impl;
import com.atguigu.springcloud.alibaba.dao.AccountDao;
import com.atguigu.springcloud.alibaba.service.AccountService ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
/**
-
账户业务实现类
*/
@Service
public class AccountServiceImpl implements AccountService {private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
AccountDao accountDao;/**
-
扣减账户余额
*/
@Override
public void decrease(Long userId, BigDecimal money) {LOGGER.info("------->account-service中扣减账户余额开始");
// 模拟超时异常
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
}
-
再次访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,报错
![](https://gitee.com/honourer/picturebed/raw/master/SpringCloudAlibaba/QQ截图20240205130846.png)
![](https://gitee.com/honourer/picturebed/raw/master/SpringCloudAlibaba/QQ截图20240205131059.png)
结论:
- 当库存和账户余额扣减后,订单状态并没有设置为已经完成,没有从零改为1
- **由于feign的重试机制,账户余额还有可能被多次扣减**
修改seata-order-service2001服务的OrderServiceImpl
```java
package com.atguigu.springcloud.alibaba.service.impl;
import com.atguigu.springcloud.alibaba.dao.OrderDao;
import com.atguigu.springcloud.alibaba.domain.Order;
import com.atguigu.springcloud.alibaba.service.AccountService;
import com.atguigu.springcloud.alibaba.service.OrderService;
import com.atguigu.springcloud.alibaba.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
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
@GlobalTransactional(name = "fsp_create_order", rollbackFor = Exception.class)
public void create(Order order){
log.info("----->开始新建订单");
//新建订单
orderDao.create(order);
//扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//修改订单状态,从零到1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了");
}
}
再次访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,报错,但数据库的数据没有变化,说明正常回滚了。
至此,Seata解决分布式的跨数据库的多个微服务调用之间的全局事务控制问题的实战完成。
标签:实战,系列,Seata,atguigu,springframework,alibaba,org,import,com From: https://www.cnblogs.com/wzzzj/p/18039388