irpas技术客

Seata概念以及简单应用_温JZ_seata

大大的周 5769

文章目录 SpringCloud Alibaba Seata处理分布式事务1.分布式事务问题由来2.什么是Seata?3.分布式事务的相关概念4.Seata Server的安装和使用4.1 Seata修改配置文件4.2seata的启动 5.Seata实例5.1业务声明5.2数据库准备5.3业务逻辑编写5.3.1 seata-order-service5.3.2 seata-storage-service5.3.3seata-account-service 6.Seata@GlobalTranscational验证6.1没加@GlobalTranscational6.2加上@GlobalTranscational之后 参考尚硅谷

SpringCloud Alibaba Seata处理分布式事务 1.分布式事务问题由来

我们之前都是一个应用程序连一个库,而且应用程序和数据库被放在同一台机器上,但是在分布式微服务的系统架构中,将原来的三个模块拆分成三个独立的应用,分别使用三个独立的数据源。如下图所示

1.仓储服务:对给定商品扣除仓储数量。

2.订单服务:根据采购需求创建订单。

3.账户服务:从用户账户中扣除余额。

上述操作都需要操作相应的数据库,但是如何保证数据库的全局一致性是一个很大的问题。一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

2.什么是Seata?

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

3.分布式事务的相关概念

分布式事务处理过程的一ID+三组件模型,一ID即Transaction ID XID,全局唯一的事务ID。

三组件:

1.TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

2.TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

3.RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

然后我们看一下Seata处理流程

如图所示一个TC管理全局,一个XID带着TC,TM,RM。

1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。

2.XID在微服务调用链路的上下文中传播。

3.RM向TC注册分支事务,将其纳入XID对应的全局事务的管辖。

4.TM向TC发起针对XID的全局提交或回滚决议。

5.TC调度XID下管辖的全部分支事务完成提交或者回滚请求。

4.Seata Server的安装和使用

下载地址Tags · seata/seata (github.com)

4.1 Seata修改配置文件

下载之后解压到自己的目录下,然后修改file.conf文件

修改service模块的group自己随便写一个。

然后修改store模块,他之前是mode是file文件存储,我们把他改成数据库存储。

然后是修改db模块将他的数据库配置改成你自己的主要是账号密码。然后就是建表这个不用你自己写人家给你写好了。

然后你复制粘贴里面的内容放到你自己的数据库下面执行就行了。

然后你再找到解压文件夹里面的registry.conf修改注册信息

4.2seata的启动

因为上面配置文件说这个东西得注册到nacos里面所以要想启动seata必须先启动nacos否则会报错。

启动成功后应该是这样的

5.Seata实例 5.1业务声明

这里我们创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

5.2数据库准备

首先我们创建数据库

create database seata_order; create database seata_storage; create database seata_account;

然后分别在对应的库下面建立相应的表

USE seata_order; DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) 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:已完结', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic; USE seata_storage; DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `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 '剩余库存', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '库存' ROW_FORMAT = Dynamic; INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100); USE seata_account; DROP TABLE IF EXISTS `t_account`; CREATE TABLE `t_account` ( `id` bigint(11) NOT NULL 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 NULL COMMENT '剩余可用额度', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账户表' ROW_FORMAT = Dynamic; INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);

然后分别建立相应的回滚日志

DROP TABLE IF EXISTS `undo_log`; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

最后数据库结构应该是这样的

5.3业务逻辑编写 5.3.1 seata-order-service

新建一个module端口号为2001。

pom.xml

<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.0.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>

这个pom文件一定要加上这三个依赖,nacos,seata,feign,我们将seata注册到nacos里,所以得有nacos,通过feign调用微服务。

yml文件

server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: tx-service-group: fsp_tx_group #自定义事务组名称需要与seata-server中的对应 nacos: discovery: server-addr: localhost:8848 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml

除了要填加yml文件以外seata还要配置file.conf,registry.conf,这两个文件就是一开始seata-server服务器得配置文件

file.conf

service { #transaction service group mapping vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称 #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #disable seata disableGlobalTransaction = false } ## transaction log store, only used in seata-server store { ## store mode: file、db mode = "db" ## file store property file { ## store location dir dir = "sessionStore" } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://localhost:3306/seata" user = "root" password = "root" } }

registry.conf

registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }

然后接下来实现我们的业务方法,我们的业务是这样的,下订单之后我们要做两个操作1.新建订单2.修改订单状态从0改为1

@Mapper public interface OrderDao { //1 新建订单 void create(Order order); //2 修改订单状态,从零改为1 void update(@Param("userId") Long userId, @Param("status") Integer status); }

mapper.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.wjz.springcloud.alibaba.dao.OrderDao"> <resultMap id="order" type="com.wjz.springcloud.alibaba.domain.Order"> <result property="id" column="id" jdbcType="BIGINT"/> <result property="userId" column="user_id" jdbcType="BIGINT"/> <result property="productId" column="product_id" jdbcType="BIGINT"/> <result property="count" column="count" jdbcType="INTEGER"/> <result property="money" column="money" jdbcType="BIGINT"/> <result property="status" column="status" jdbcType="INTEGER"/> </resultMap> <insert id="create"> insert into t_order(user_id, product_id, count, money, status) value (#{userId},#{productId},#{count},#{money},0) </insert> <update id="update"> update t_order set status = 1 where user_id = #{userId} and status = #{status} </update> </mapper>

然后是order的service层和Impl层

public interface OrderService { void create(Order order); } @Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private StorageService storageService; @Resource private AccountService accountService; /** * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态 * 简单说:下订单->扣库存->减余额->改状态 * 注释掉 @GlobalTransactional 的时候,需要注意下方这个方法里面手动模拟了延时,也需要注释掉 * com.atguigu.springcloud.alibaba.service.impl.AccountServiceImpl#decrease(java.lang.Long, java.math.BigDecimal) */ @Override public void create(Order order) { log.info("----->开始新建订单"); //1 新建订单 orderDao.create(order); //2 扣减库存 log.info("----->订单微服务开始调用库存,做扣减Count"); storageService.decrease(order.getProductId(), order.getCount()); log.info("----->订单微服务开始调用库存,做扣减end"); //3 扣减账户 log.info("----->订单微服务开始调用账户,做扣减Money"); accountService.decrease(order.getUserId(), order.getMoney()); log.info("----->订单微服务开始调用账户,做扣减end"); //4 修改订单状态,从零到1,1代表已经完成 log.info("----->修改订单状态开始"); orderDao.update(order.getUserId(), 0); log.info("----->修改订单状态结束"); log.info("----->下订单结束了,O(∩_∩)O哈哈~"); } }

新建订单之后我们要扣减库存和扣减账户所以我们要写storageService和accountService这两个的实现。

@FeignClient(value = "seata-storage-service") public interface StorageService { @PostMapping(value = "/storage/decrease") CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); } @FeignClient(value = "seata-account-service") public interface AccountService { /** * @param userId * @param money * @return */ @PostMapping(value = "/account/decrease") CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }

减库存和扣减账户我们通过远程调用这两个的微服务来实现。然后是controller

@RestController public class OrderController { @Resource private OrderService orderService; @GetMapping("/order/create")//这个浏览器只能用get请求访问,我们底层调用微服务的时候会使用PostMapping public CommonResult create(Order order) { orderService.create(order); return new CommonResult(200, "订单创建成功"); } }

然后就是一些配置类使用Seata对我们的数据源进行代理

@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(); } } @Configuration @MapperScan({"com.wjz.springcloud.alibaba.dao"}) public class MyBatisConfig { }

最后还有启动类

@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//一定要加这个忽略掉之前用我们配置的Seata代理 public class SeataMainApp2001 { public static void main(String[] args) { SpringApplication.run(SeataMainApp2001.class, args); System.out.println("启动成功"); } } 5.3.2 seata-storage-service

pom文件

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://·mit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" disableGlobalTransaction = false } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }

registry.conf

registry { # file 、nacos 、eureka、redis、zk type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6381" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } file { name = "file.conf" } } config { # file、nacos 、apollo、zk type = "file" nacos { serverAddr = "localhost" namespace = "" cluster = "default" } apollo { app.id = "fescar-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } file { name = "file.conf" } }

controller类

@RestController public class StorageController { @Resource private StorageService storageService; /** * 扣减库存 */ @RequestMapping("/storage/decrease") public CommonResult decrease(Long productId, Integer count) { storageService.decrease(productId, count); return new CommonResult(200, "扣减库存成功!"); } }

service类

public interface StorageService { /** * 扣减库存 * * @param productId * @param count */ void decrease(Long productId, Integer count); }

impl类

@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中扣减库存结束"); } }

mapper类

@Mapper public interface StorageDao { //扣减库存 void decrease(@Param("productId") Long productId, @Param("count") Integer count); }

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.wjz.springcloud.alibaba.dao.StorageDao"> <resultMap id="BaseResultMap" type="com.wjz.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>

domain(实体类)

@Data @AllArgsConstructor @NoArgsConstructor public class Storage { private Long id; /** * 产品id */ private Long productId; /** * 总库存 */ private Integer total; /** * 已用库存 */ private Integer used; /** * 剩余库存 */ private Integer residue; } 5.3.3seata-account-service

yml文件改个端口号和文件名就行了,conf文件和2002一样,config文件和2002一样,唯一不同的就是业务类。

controller类

@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,"扣减账户余额成功!"); } }

service类

public interface AccountService { /** * 扣减账户余额 * @param userId 用户id * @param money 金额 */ void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }

impl类

@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中扣减账户余额开始"); accountDao.decrease(userId, money); LOGGER.info("------->account-service中扣减账户余额结束"); } }

domain(实体类)

@Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; /** * 用户id */ private Long userId; /** * 总额度 */ private BigDecimal total; /** * 已用额度 */ private BigDecimal used; /** * 剩余额度 */ private BigDecimal residue; }

mapper类

@Mapper public interface AccountDao { /** * 扣减账户余额 */ void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money); }

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.wjz.springcloud.alibaba.dao.AccountDao"> <resultMap id="BaseResultMap" type="com.wjz.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>

主启动类上面要加上@SpringBootApplication(exclude = DataSourceAutoConfiguration.class),@EnableDiscoveryClient,@EnableFeignClients注解。

6.Seata@GlobalTranscational验证

我们要对比一下没加这个注解之前出异常的结果和加注解之后出异常了如何处理。我们先启动2001,2002,2003这个三个微服务然后访问2001的下订单的接口。

我们先看一下数据库的初始情况

这个是订单表,因为一开始没人下单所以一开始的数据都是空的。

库存表之前的总库存是100,已用库存为0,剩余库存也是100

这张是账户表因为啥也没买所以used为0

6.1没加@GlobalTranscational

然后我们先下一个订单观察数据库的变化。

1号顾客买了1号商品数量是10个,花了100元状态是1已完成。

库存还剩90个。

账户表花了100还剩900。

然后我们模拟一下超时异常在Account账户减少这块让他睡20秒钟。

@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中扣减账户余额结束"); } }

可以发现我们访问同样的地址由于存在超时异常所以会报错。

那既然在支付的时候超时了那么这一单肯定是失败的所以status是0下单失败。然后我们看库存表,

既然这一单失败了他居然减少了10就剩80了,这肯定是不对的。然后看看账户表

下单失败结果我还被扣钱了,这也是不对滴。

6.2加上@GlobalTranscational之后

既然没加这个注解会产生问题,那么我们再看看加上之后对全局事务进行控制看看能不能解决问题

@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("----->开始新建订单"); //1 新建订单 orderDao.create(order); //2 扣减库存 log.info("----->订单微服务开始调用库存,做扣减Count"); storageService.decrease(order.getProductId(), order.getCount()); log.info("----->订单微服务开始调用库存,做扣减end"); //3 扣减账户 log.info("----->订单微服务开始调用账户,做扣减Money"); accountService.decrease(order.getUserId(), order.getMoney()); log.info("----->订单微服务开始调用账户,做扣减end"); //4 修改订单状态,从零到1,1代表已经完成 log.info("----->修改订单状态开始"); orderDao.update(order.getUserId(), 0); log.info("----->修改订单状态结束"); log.info("----->下订单结束了,O(∩_∩)O哈哈~"); } }

然后我们再访问之前的接口

加了这个全局事务控制的注解并不会改变什么依然报错但是我们来看看数据库

发现这次出现异常的订单根本不会被落库。因为你出现异常了我就会回滚导致没有错误数据进库。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Seata #文章目录springcloud #Alibaba #Server的安装和使用41