大白话讲解分布式事务-SEATA事务四种模式(内含demo)

因为这里主要是讲解分布式事务,关于什么是事务,以及事务的特性,单个事务的使用方式,以及在Spring框架下,事务的传播方式,这里就不再赘述了。但是我这里要补充一点就是,一提到事务大家脑子里第一闪念就是传统意义上的事务,就是指数据库上的事务,实则在我们正常的开发中,更多的关注的是数据的一致性。

但是又因为我们的数据不仅仅是存储在数据库上,也可能是别的组件上,或者是别的不支持事务的数据库上。时至今日,我们的“事务”的概念已经逐渐泛化掉了,在很多场景下,如果只是讲我们的数据库的事务显得过于狭隘,终极目的就是为了保持数据的一致性。因为数据的事务比较有代表性,而且每一位开发的同学都是从这里接触的事务,所以我下面也主要以数据库来讲,但不仅仅是指数据库的事务。

数据库的事务的生效范围是数据库连接级别的。我们先说一下这里的连接是什么意思,无论是我们通过navicat或者别的方式,比如JDBC操作数据库,其实都是基于连接。我想大家一定要理解“事务基于连接而不是基于数据库”这句话的意思,就是说即便我操作的是同一个数据库,不同连接上的事务仍然是两个事务,而不是说我只有都对两个不同的数据库的操作才是两个事务。

  • 当我们使用navicat的时候,我们连上数据库开的窗口那就是一个数据库连接

navicat数据库连接

  • JDBC中先获取数据库连接才能开启并操作事务
try {Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);// 禁用自动提交,开始手动管理事务connection.setAutoCommit(false);// 执行DMLString deductSql = "UPDATE account SET balance = balance - ? WHERE account_id = ?";deductStmt = connection.prepareStatement(deductSql);deductStmt.setDouble(1, 100); // 扣减金额deductStmt.setInt(2, 1); // 账户A的IDdeductStmt.executeUpdate();// 提交事务 connection.commit();
} catch (Exception e) {// 出现异常回滚connection.rollback();
}

从上面大家已经知道事务是基于连接级别的。也就是说在一个连接上某一瞬间同时只能有一个事务存在。当你在两个不同的连接上同时操作两个事务,两个事务是相互独立的,但是效果都会体现在数据库上。如果是两个事务同时开启并操作相同的数据,这种冲突当前的数据库会根据设置的隔离级别来进行处理,由数据库本身保证。

上面已经说了我们的事务都是基于连接的。如果在一次请求进来,在处理这一个请求期间,我们多次执行数据库操作,如果每次操作数据库的时候每次都会重新获取新的连接(现在大家都会配置数据库连接池,所以存在多次获取同一个连接的可能性),无论获取的是不是同一个数据库连接,只要是设置的是自动提交,数据库连接默认都是自动提交,就没办法保证整个请求生命周期中的数据符合事务一致性。

想必大家已经明白事务是基于连接的意义了,而连接又是面向数据库的。如果我们在一次请求中操作了同一个数据库,其中的事务我们叫做集中式事务,这个是比较好保证的,只需要我们在整个请求过程中保持使用的是同一个数据库连接,如果全部执行成功就commit,所有的数据库操作全部生效。如果失败就rollback,所有的数据库操作全部失效。本质可以参考上面的jdbc demo,其实Spring框架的事务就是基于这个原理做的。详情可以看大白话讲解Spring对数据源和事务管理以及多数据源配置

众所周知(你真知道吗?评论区告诉我),Spring的事务是基于事务管理器 @Transactional(transactionManager = "primaryTransactionManager"),事务管理器是基于数据源。

// 配置主数据源的事务管理器
@Primary
@Bean(name = "primaryTransactionManager")
public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}

Spring支持多个数据源的配置,也就支持多个事务管理器,也就支持多个不同数据源的事务,但是没办法保证不同数据源的事务保持一致,不同的数据源是在不同的事务上下文处理的,这种情况它们已经是分布式事务了。

如果是操作了不同的数据库,要么对所有数据库的操作全部成功,要么对所有数据库的操作全部回滚,这种场景的事务,我们叫做分布式事务。解释下,分布式事务是我们需要保证全局的一致性,无论是不是数据库,有可能我们把数据更新到Redis、Mongo或者别的保存数据状态的组件上。所以分布式事务只是一种对数据状态全局一致性的保证。

分布式事务,目前市场上比较成熟的阿里的开源的 SEATA分布式事务框架。

分布式事务的四种模式:

  • Seata AT 模式
  • Seata TCC 模式
  • Seata Saga 模式
  • Seata XA 模式

四种模式对比

模式一致性适用场景性能开发复杂度数据库支持
AT最终一致性数据库为主的简单场景关系型数据库
TCC强一致性复杂业务逻辑较高多种存储类型
SAGA最终一致性长事务、异步场景较高较高不限
XA强一致性金融类强一致性需求较低支持 XA 的数据库

AT模式

AT 模式是 Seata 中的核心模式,适用于关系型数据库的分布式事务处理。以下是一个使用 AT 模式的简单示例,演示如何在 Spring Boot 中集成 Seata 来处理分布式事务。假设有两个微服务 order-serviceaccount-service,它们分别负责订单处理和账户扣款。

1. 环境准备

  • Spring Boot
  • MySQL
  • Seata Server
  • Nacos(用于服务发现和配置管理)

2. 项目结构

假设我们有两个模块:

  • order-service:负责生成订单
  • account-service:负责扣减账户余额

每个服务都使用 Spring Boot 和 MySQL,并且通过 Seata 来保证分布式事务的一致性。

seata-demo/
├── account-service
├── order-service
└── seata-server (单独运行的 Seata Server)

3. 依赖引入

每个微服务都需要引入以下依赖:

<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

4. 配置 Seata

order-serviceaccount-serviceapplication.yml 中添加 Seata 和 Nacos 的配置。
spring:application:name: order-servicecloud:nacos:discovery:server-addr: localhost:8848datasource:url: jdbc:mysql://localhost:3306/order_dbusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourceseata:tx-service-group: my_test_tx_groupfeign:discovery:enabled: trueserver:port: 8081seata:enabled: trueservice:vgroup-mapping:my_test_tx_group: "default"disable-global-transaction: false

5. 数据库表结构

order 表(订单服务中的订单表)
CREATE TABLE `order_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`commodity_code` VARCHAR(255) DEFAULT NULL,`count` INT(11) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);
account 表(账户服务中的账户表)
CREATE TABLE `account_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);

6. Order Service 实现

order-service 中实现订单创建逻辑,并使用 Seata 的全局事务。
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/create")public String create(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count, @RequestParam("money") BigDecimal money) {orderService.createOrder(userId, commodityCode, count, money);return "Order created successfully!";}
}@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate AccountFeignClient accountFeignClient;@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)public void createOrder(String userId, String commodityCode, Integer count, BigDecimal money) {// 1. 创建订单Order order = new Order();order.setUserId(userId);order.setCommodityCode(commodityCode);order.setCount(count);order.setMoney(money);orderRepository.save(order);// 2. 调用账户服务扣减余额accountFeignClient.debit(userId, money);// 3. 模拟异常if (money.compareTo(BigDecimal.TEN) > 0) {throw new RuntimeException("Money exceeds limit, transaction rollback!");}}
}

7. Account Service 实现

account-service 中实现扣减账户余额的逻辑。
@RestController
@RequestMapping("/account")
public class AccountController {@Autowiredprivate AccountService accountService;@PostMapping("/debit")public String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money) {accountService.debit(userId, money);return "Account debited successfully!";}
}@Service
public class AccountService {@Autowiredprivate AccountRepository accountRepository;public void debit(String userId, BigDecimal money) {Account account = accountRepository.findByUserId(userId);account.setMoney(account.getMoney().subtract(money));accountRepository.save(account);}
}

8. Feign Client

order-service 中通过 Feign 客户端调用 account-service

@FeignClient(name = "account-service")
public interface AccountFeignClient {@PostMapping("/account/debit")String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}

9. 启动 Seata Server

启动 Seata Server,并确保配置文件 registry.conf 中的注册中心指向 Nacos。

sh ./seata-server.sh -p 8091 -h 127.0.0.1

10. 测试分布式事务

通过调用 order-service 的接口 /order/create 创建订单并触发分布式事务。Seata 会确保在 order-serviceaccount-service 之间的一致性。如果中间出现异常,Seata 会自动回滚两个服务中的事务。

curl -X POST "http://localhost:8081/order/create?userId=U1001&commodityCode=C00321&count=2&money=20"

总结

以上是一个简单的 Seata AT 模式的分布式事务处理示例。在这个示例中,order-serviceaccount-service 分别管理订单和账户余额,Seata 确保了它们之间的事务一致性。

TCC模式

TCC(Try-Confirm-Cancel)模式是 Seata 中的一种分布式事务解决方案,通过分离业务操作的三个步骤(Try、Confirm、Cancel)来保证分布式事务的一致性。以下是一个 TCC 模式的简单示例,展示如何使用 Seata 的 TCC 模式在 Spring Boot 中处理分布式事务。

场景

假设有两个微服务:

  • order-service: 负责创建订单。
  • account-service: 负责扣减账户余额。

TCC 模式下,每个服务都需要实现三种逻辑:

  • Try: 尝试执行业务,预留资源。
  • Confirm: 确认操作,提交业务。
  • Cancel: 取消操作,回滚预留的资源。

1. 项目结构

seata-tcc-demo/
├── account-service
├── order-service
└── seata-server

2. 依赖引入

为两个服务引入必要的依赖(TCC 模式专用)。

<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3. TCC Resource 接口定义

TCC 模式中,每个服务都需要实现 Try、Confirm 和 Cancel 逻辑。在 account-serviceorder-service 中实现 TCC 资源接口。

account-service 为例:
public interface AccountTccAction {/*** Try: 预留资源(扣减余额)*/@TwoPhaseBusinessAction(name = "accountTccAction", commitMethod = "commit", rollbackMethod = "rollback")boolean prepareDecreaseAccount(BusinessActionContext context, String userId, BigDecimal money);/*** Confirm: 提交*/boolean commit(BusinessActionContext context);/*** Cancel: 回滚*/boolean rollback(BusinessActionContext context);
}

4. AccountTccActionImpl 实现

@Service
public class AccountTccActionImpl implements AccountTccAction {@Autowiredprivate AccountRepository accountRepository;// Try 阶段:预留资源,减少账户余额@Overridepublic boolean prepareDecreaseAccount(BusinessActionContext context, String userId, BigDecimal money) {Account account = accountRepository.findByUserId(userId);if (account.getMoney().compareTo(money) < 0) {throw new RuntimeException("Insufficient balance");}// 扣减可用余额,标记冻结金额account.setFrozenMoney(account.getFrozenMoney().add(money));account.setMoney(account.getMoney().subtract(money));accountRepository.save(account);return true;}// Confirm 阶段:确认提交,正式扣减余额@Overridepublic boolean commit(BusinessActionContext context) {String userId = context.getActionContext("userId").toString();BigDecimal money = new BigDecimal(context.getActionContext("money").toString());Account account = accountRepository.findByUserId(userId);// 解除冻结金额account.setFrozenMoney(account.getFrozenMoney().subtract(money));accountRepository.save(account);return true;}// Cancel 阶段:取消操作,回滚冻结的余额@Overridepublic boolean rollback(BusinessActionContext context) {String userId = context.getActionContext("userId").toString();BigDecimal money = new BigDecimal(context.getActionContext("money").toString());Account account = accountRepository.findByUserId(userId);// 恢复余额account.setFrozenMoney(account.getFrozenMoney().subtract(money));account.setMoney(account.getMoney().add(money));accountRepository.save(account);return true;}
}

5. Order Service 实现

OrderTccAction 定义:
public interface OrderTccAction {@TwoPhaseBusinessAction(name = "orderTccAction", commitMethod = "commit", rollbackMethod = "rollback")boolean prepareCreateOrder(BusinessActionContext context, String userId, String commodityCode, Integer count, BigDecimal money);boolean commit(BusinessActionContext context);boolean rollback(BusinessActionContext context);
}
OrderTccActionImpl 实现:
@Service
public class OrderTccActionImpl implements OrderTccAction {@Autowiredprivate OrderRepository orderRepository;@Overridepublic boolean prepareCreateOrder(BusinessActionContext context, String userId, String commodityCode, Integer count, BigDecimal money) {// 预留订单(只写入订单状态为“处理中”)Order order = new Order();order.setUserId(userId);order.setCommodityCode(commodityCode);order.setCount(count);order.setMoney(money);order.setStatus("processing");orderRepository.save(order);return true;}@Overridepublic boolean commit(BusinessActionContext context) {// 提交事务,将订单状态改为“已完成”Long orderId = Long.valueOf(context.getActionContext("orderId").toString());Order order = orderRepository.findById(orderId).orElse(null);if (order != null) {order.setStatus("completed");orderRepository.save(order);}return true;}@Overridepublic boolean rollback(BusinessActionContext context) {// 回滚事务,删除订单或恢复订单状态Long orderId = Long.valueOf(context.getActionContext("orderId").toString());Order order = orderRepository.findById(orderId).orElse(null);if (order != null) {orderRepository.delete(order);}return true;}
}

6. Feign Client 实现

order-service 调用 account-service 来执行账户扣款。

@FeignClient(name = "account-service")
public interface AccountFeignClient {@PostMapping("/account/tcc/prepare")boolean prepareDecreaseAccount(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}

7. Global Transaction(全局事务控制)

order-service 中通过全局事务控制进行 TCC 操作:

@Service
public class OrderService {@Autowiredprivate OrderTccAction orderTccAction;@Autowiredprivate AccountFeignClient accountFeignClient;@GlobalTransactional(name = "create-order-tcc", rollbackFor = Exception.class)public void createOrder(String userId, String commodityCode, Integer count, BigDecimal money) {// 调用订单服务的 TCC Try 阶段orderTccAction.prepareCreateOrder(null, userId, commodityCode, count, money);// 调用账户服务的 TCC Try 阶段accountFeignClient.prepareDecreaseAccount(userId, money);// 模拟异常情况,触发事务回滚if (money.compareTo(BigDecimal.TEN) > 0) {throw new RuntimeException("Money exceeds limit, transaction rollback!");}}
}

8. 测试

启动 Seata Server 并运行服务后,可以通过调用 order-service 的接口 /order/create 创建订单并触发 TCC 模式的全局事务。

curl -X POST "http://localhost:8081/order/create?userId=U1001&commodityCode=C00321&count=2&money=5"

总结

这个简单的 TCC 模式示例展示了如何在分布式系统中通过 Try-Confirm-Cancel 三阶段控制分布式事务的一致性。在这个示例中,order-serviceaccount-service 通过 TCC 模式的接口实现,确保了分布式事务在各个服务中的一致性。

XA模式

XA 模式是分布式事务中的一种标准化协议,由 X/Open 组织提出,支持两阶段提交协议(2PC, Two-Phase Commit)。它通过协调器协调各参与者的提交和回滚操作,以保证分布式事务的一致性。Seata 的 XA 模式利用数据库本身的 XA 接口来实现分布式事务管理。下面是一个基于 Seata 的 XA 模式的简单示例。

场景

假设有两个微服务:

  • order-service:负责订单的创建。
  • account-service:负责扣减账户余额。

XA 模式通过数据库的本地事务实现分布式事务的提交和回滚,Seata 作为事务协调器,协调这些本地事务的执行。

1. 项目结构

seata-xa-demo/
├── account-service
├── order-service
└── seata-server

2. 依赖引入

在每个服务中都需要引入 Seata 和 Spring Boot 的相关依赖:

<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3. 数据库配置

XA 模式依赖于数据库的本地事务支持。数据库需要开启 XA 支持。常见的关系型数据库(如 MySQL、PostgreSQL 等)大部分都支持 XA 事务。

配置数据源(MySQL):
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/order_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTCusername: rootpassword: roottype: com.zaxxer.hikari.HikariDataSourcexa:enabled: true

4. 配置 Seata

order-serviceaccount-service 中的 application.yml 中配置 Seata XA 模式。

seata:tx-service-group: my_test_tx_groupmode: XA

5. 数据库表结构

order_tbl(订单表):
CREATE TABLE `order_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`commodity_code` VARCHAR(255) DEFAULT NULL,`count` INT(11) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);
account_tbl(账户表):
CREATE TABLE `account_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);

6. Order Service 实现

OrderController
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/create")public String create(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count, @RequestParam("money") BigDecimal money) {orderService.createOrder(userId, commodityCode, count, money);return "Order created successfully!";}
}
OrderService
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate AccountFeignClient accountFeignClient;@GlobalTransactional(name = "create-order-xa", rollbackFor = Exception.class)public void createOrder(String userId, String commodityCode, Integer count, BigDecimal money) {// 1. 创建订单Order order = new Order();order.setUserId(userId);order.setCommodityCode(commodityCode);order.setCount(count);order.setMoney(money);orderRepository.save(order);// 2. 调用账户服务扣减余额accountFeignClient.debit(userId, money);// 3. 模拟异常if (money.compareTo(BigDecimal.TEN) > 0) {throw new RuntimeException("Money exceeds limit, transaction rollback!");}}
}

7. Account Service 实现

AccountController
@RestController
@RequestMapping("/account")
public class AccountController {@Autowiredprivate AccountService accountService;@PostMapping("/debit")public String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money) {accountService.debit(userId, money);return "Account debited successfully!";}
}
AccountService
@Service
public class AccountService {@Autowiredprivate AccountRepository accountRepository;@Transactionalpublic void debit(String userId, BigDecimal money) {Account account = accountRepository.findByUserId(userId);if (account.getMoney().compareTo(money) < 0) {throw new RuntimeException("Insufficient balance");}account.setMoney(account.getMoney().subtract(money));accountRepository.save(account);}
}

8. Feign Client

order-service 使用 Feign Client 调用 account-service 的扣减余额操作。

@FeignClient(name = "account-service")
public interface AccountFeignClient {@PostMapping("/account/debit")String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}

9. 启动 Seata Server

启动 Seata Server,确保它能够正确注册到 Nacos 或者其他注册中心,并将事务日志写入数据库。

sh ./seata-server.sh -p 8091 -h 127.0.0.1

10. 测试

通过调用 order-service 的接口 /order/create 创建订单并触发 XA 模式的分布式事务。

curl -X POST "http://localhost:8081/order/create?userId=U1001&commodityCode=C00321&count=2&money=5"

这将会触发订单创建和账户扣款的操作,Seata 的 XA 模式会确保这两个操作在不同的数据库上保持一致性。如果在任何服务中发生异常,Seata 会回滚事务,确保数据的一致性。

11. 总结

Seata 的 XA 模式依赖于数据库本地事务接口,通过两阶段提交(2PC)协议保证多个数据库实例间的事务一致性。在该示例中,order-serviceaccount-service 都通过 Seata 协调执行分布式事务操作,确保了订单创建和账户扣款操作的原子性。

SEATA模式

以下是一个完整的 Seata 分布式事务的示例,展示如何在 Spring Boot 项目中使用 Seata 进行分布式事务管理。

场景

假设有两个微服务:

  • order-service: 负责创建订单。
  • account-service: 负责扣减账户余额。

当创建订单的同时,账户余额也需要扣除,这些操作需要在一个分布式事务中进行,以确保数据一致性。

1. 项目结构

seata-demo/
├── account-service
├── order-service
└── seata-server

2. 依赖引入

在两个服务中都需要引入以下 Seata 相关依赖:

<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3. 配置 Seata

application.yml 中配置 Seata,并确保 Seata 使用的注册中心和配置中心,比如 Nacos:

seata:tx-service-group: my_test_tx_groupservice:vgroup-mapping:my_test_tx_group: "default"enable-auto-data-source-proxy: truedata-source-proxy-mode: AT

4. 数据库表结构

在两个服务的数据库中创建以下表:

order_tbl(订单表):
CREATE TABLE `order_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`commodity_code` VARCHAR(255) DEFAULT NULL,`count` INT(11) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);
account_tbl(账户表):
CREATE TABLE `account_tbl` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` VARCHAR(255) DEFAULT NULL,`money` DECIMAL(11, 0) DEFAULT NULL,PRIMARY KEY (`id`)
);

5. Account Service 实现

account-service 负责扣减账户余额。

AccountController
@RestController
@RequestMapping("/account")
public class AccountController {@Autowiredprivate AccountService accountService;@PostMapping("/debit")public String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money) {accountService.debit(userId, money);return "Account debited successfully!";}
}
AccountService
@Service
public class AccountService {@Autowiredprivate AccountRepository accountRepository;@Transactionalpublic void debit(String userId, BigDecimal money) {Account account = accountRepository.findByUserId(userId);if (account.getMoney().compareTo(money) < 0) {throw new RuntimeException("Insufficient balance");}account.setMoney(account.getMoney().subtract(money));accountRepository.save(account);}
}
AccountRepository
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {Account findByUserId(String userId);
}

6. Order Service 实现

order-service 负责创建订单。

OrderController
@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/create")public String create(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count, @RequestParam("money") BigDecimal money) {orderService.createOrder(userId, commodityCode, count, money);return "Order created successfully!";}
}
OrderService
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate AccountFeignClient accountFeignClient;@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)public void createOrder(String userId, String commodityCode, Integer count, BigDecimal money) {// 1. 创建订单Order order = new Order();order.setUserId(userId);order.setCommodityCode(commodityCode);order.setCount(count);order.setMoney(money);orderRepository.save(order);// 2. 调用账户服务扣减余额accountFeignClient.debit(userId, money);// 3. 模拟异常if (money.compareTo(BigDecimal.TEN) > 0) {throw new RuntimeException("Money exceeds limit, transaction rollback!");}}
}
OrderRepository
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}

7. Feign Client

order-service 使用 Feign Client 调用 account-service 的扣减余额操作。

@FeignClient(name = "account-service")
public interface AccountFeignClient {@PostMapping("/account/debit")String debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}

8. 启动 Seata Server

确保 Seata Server 已启动,并注册到 Nacos 或者其他服务注册中心。

sh ./seata-server.sh -p 8091 -h 127.0.0.1

9. 测试

通过调用 order-service 的接口 /order/create 创建订单并触发分布式事务:

curl -X POST "http://localhost:8081/order/create?userId=U1001&commodityCode=C00321&count=2&money=5"

10. 总结

在这个简单的 Seata Demo 中,我们通过 order-serviceaccount-service 演示了 Seata 的 AT 模式如何保证分布式事务的一致性。在 order-service 中,通过 Seata 的 @GlobalTransactional 注解,将订单创建和账户扣款的操作放在同一个全局事务中,确保了跨服务操作的原子性。如果任一操作失败,事务将回滚,保证数据一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/459049.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LLM论文日更 | 一种引入上下文的文档嵌入方法 】

论文&#xff1a;​​​​​​​https://arxiv.org/pdf/2410.02525代码&#xff1a;暂未开源机构&#xff1a;康奈尔大学领域&#xff1a;embedding model发表&#xff1a;arxiv 研究背景 研究问题&#xff1a;这篇文章要解决的问题是如何改进文档嵌入&#xff0c;使其在特定上…

短短一年多,ChatGPT 发展到什么程序了?

短短一年多&#xff0c;ChatGPT 就与 Google、Youtube、X.com等大佬级网站比肩。成为全球访问量最大的网站之一 爆发期&#xff0c;访问量月增长率高达3000%左右。网站流量从1800万次访问激增至6.72亿次只花了60来天。 2023年一项AI产品访问量统计结果中ChatGPT占比60%&#xf…

信号反射与振铃

反射来源于阻抗不匹配&#xff0c;振铃就是多次反射 对于反射要记住传输系数与反射系数 传输系数与反射系数 振铃现象计算说明

Python轴承故障诊断 (15)基于CNN-Transformer的一维故障信号识别模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客 三十多个开源…

Spark原理

主要包括&#xff1a; 核心组件的运行机制&#xff08;Master&#xff0c;Worker&#xff0c;SparkContext等&#xff09;任务调度的原理Shuffile的原理内存管理数据倾斜处理Spark优化 核心组件的运行机制 Spark 执行任务的原理&#xff1a; Spark on Yarn: Cluster模型&am…

【数据结构-邻项消除】力扣1003. 检查替换后的词是否有效

给你一个字符串 s &#xff0c;请你判断它是否 有效 。 字符串 s 有效 需要满足&#xff1a;假设开始有一个空字符串 t “” &#xff0c;你可以执行 任意次 下述操作将 t 转换为 s &#xff1a; 将字符串 “abc” 插入到 t 中的任意位置。形式上&#xff0c;t 变为 tleft “…

GPT-4o 和 GPT-4 Turbo 模型之间的对比

GPT-4o 和 GPT-4 Turbo 之间的对比 备注 要弄 AI &#xff0c;不同模型之间的对比就比较重要。 GPT-4o 是 GPT-4 Turbo 的升级版本&#xff0c;能够提供比 GPT-4 Turbo 更多的内容和信息&#xff0c;但成功相对来说更高一些。 第三方引用 在 2024 年 5 月 13 日&#xff0…

115页PPT华为管理变革:制度创新与文化塑造的核心实践

集成供应链&#xff08;ISC&#xff09;体系 集成供应链&#xff08;ISC&#xff09;体系是英文Integrated Supply Chain的缩写&#xff0c;是一种先进的管理思想&#xff0c;它指的是由相互间提供原材料、零部件、产品和服务的供应商、合作商、制造商、分销商、零售商、顾客等…

TCP simultaneous open测试

源代码 /*************************************************************************> File Name: common.h> Author: hsz> Brief:> Created Time: 2024年10月23日 星期三 09时47分51秒**********************************************************************…

ctfshow(175->178)--SQL注入--联合注入及其过滤

Web175 进入界面&#xff1a; 审计&#xff1a; 查询语句&#xff1a; $sql "select username,password from ctfshow_user5 where username !flag and id ".$_GET[id]." limit 1;";返回逻辑&#xff1a; if(!preg_match(/[\x00-\x7f]/i, json_enc…

可编辑PPT | 柔性制造企业数字化转型与智能工厂建设方案

这份PPT介绍了柔性制造企业在数字化转型和智能工厂建设方面的综合方案。探讨了数据采集、数字孪生、无码开发支撑、数据资产和应用能力层的构建&#xff0c;以及企业信息化的新思路。最终目标是通过这些技术和策略&#xff0c;实现供应链协同、产品全生命周期管理、绿色节能生产…

VUE, element-plus, table分页表格列增加下拉筛选多选框,请求后台

简介 为了方便表格查询时可以筛选列的值&#xff0c;需要给列增加筛选框&#xff08;多选框&#xff09;&#xff0c;element-plus提供了列的filter字段&#xff0c;但是基于表格数据的筛选&#xff0c;不会重新请求后台&#xff0c;而且当前表格数据有多少个条目&#xff0c;…

WPF+MVVM案例实战(一)- 设备状态LED灯变化实现

文章目录 1、项目创建2、UI界面布局1. MainWindow.xaml2、颜色转换器实现2.MainViewModel.cs 代码实现 3、运行效果4.源代码下载 1、项目创建 打开 VS2022 &#xff0c;新建项目 Wpf_Examples&#xff0c;创建各层级文件夹&#xff0c;安装 CommunityToolkit.Mvvm 和 Microsof…

python实现投影仪自动对焦

这是一款投影仪,它带有对焦摄像头 它是如何自动对焦的呢? 我们先看一下对焦算法展示效果 说明:左侧是原视频,右侧是对调焦后的视频帧展示,如果下一帧视频比当前帧清晰就会显示下一帧,否则,还是显示当前帧,直至找到更清晰的帧 原理说明: 在投影仪上对焦摄像头就会实…

HelloCTF [RCE-labs] Level 4 - SHELL 运算符

开启靶场&#xff0c;打开链接&#xff1a; 源码很简单&#xff0c;system("ping -c 1 $ip"); GET传参ip 构造payload&#xff1a; /?ip127.0.0.1;ls / /?ip127.0.0.1;cat /flag 成功得到flag&#xff1a; NSSCTF{04ad1d48-4530-481d-aa5d-8a153b0ebf2c}

常见学习陷阱及解决方案

文章目录 1. 拖延2. 信息过载3. 缺乏计划4. 过度依赖记忆5. 缺乏反馈6. 学习环境不佳7. 不够自信8. 不适合的学习方法结论 在学习过程中&#xff0c;学生常常会遇到各种陷阱&#xff0c;这些陷阱可能会影响学习效果和动机。以下是一些常见的学习陷阱及其解决方案&#xff1a; 1…

软硬链接_动静态库

软硬链接 软链接创建 硬链接创建 软链接是独立文件&#xff08;独立inode号&#xff09; 硬链接不是独立文件&#xff08;inode和目标相同&#xff09; 如何理解软硬链接 软链接有独立inode&#xff0c;软链接内容上&#xff0c;保存的是文件路径 硬链接不是独立文件&#xf…

服务器虚拟化全面教程:从入门到实践

服务器虚拟化全面教程&#xff1a;从入门到实践 引言 在现代 IT 基础设施中&#xff0c;服务器虚拟化已成为一种不可或缺的技术。它不仅能够提高资源利用率&#xff0c;还能降低硬件成本&#xff0c;优化管理流程。本文将深入探讨服务器虚拟化的概念、技术、应用场景及其实现…

初始JavaEE篇——多线程(6):线程池

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 到现在为止&#xff0c;我们已经学习了两个经典的多线程案例了&#xff1a;饿汉模式与懒汉模式、阻塞队列与生产者—消费者模型。想要…

static、 静态导入、成员变量的初始化、单例模式、final 常量(Content)、嵌套类、局部类、抽象类、接口、Lambda、方法引用

static static 常用来修饰类的成员&#xff1a;成员变量、方法、嵌套类 成员变量 被static修饰&#xff1a;类变量、成员变量、静态字段 在程序中只占用一段固定的内存&#xff08;存储在方法区&#xff09;&#xff0c;所有对象共享可以通过实例、类访问 (一般用类名访问和修…