在部署好seata 2.0以后,下面进行spring cloud集成seata2.0 AT模式实战环节。
首先,进行数据库环境准备,seata AT模式需要客户端应用创建undo_log表,建表语句如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-- seata_demo.undo_log definition CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; |
创建好seata框架需要的表以后,我们新建一个业务表,参考SQL如下:
|
1 2 3 4 5 6 7 8 9 10 |
-- seata_demo.account definition CREATE TABLE `account` ( `name` varchar(100) NOT NULL, `account` decimal(10,0) DEFAULT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; INSERT INTO seata_demo.account(name, account) VALUES('A', 100); INSERT INTO seata_demo.account(name, account) VALUES('B', 100); |
数据库环境准备好以后,创建一个spring boot项目,我这里使用的spring boot版本是2.7.18,导入相关依赖:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.5.0</version> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> |
编写AccountDO类:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class AccountDO { private String name; private double account; } |
编写Mapper层方法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import com.seata.demo.vo.AccountDO; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Update; @Mapper public interface AccountMapper { @Insert("INSERT INTO account\n" + "(name, account)" + "VALUES(#{name}, #{account});") int insert(AccountDO accountVo); @Update("UPDATE account " + "SET account=account+#{account}" + "WHERE name=#{name};") int add (AccountDO accountVo); } |
编写Service层类:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import com.seata.demo.mapper.AccountMapper; import com.seata.demo.vo.AccountDO; import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountService { @Autowired private AccountMapper accountMapper; @GlobalTransactional public int save(AccountDO accountVo) { return accountMapper.insert(accountVo); } public int add(AccountDO accountVo) { return accountMapper.add(accountVo); } } |
编写controller层类:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import com.seata.demo.service.AccountService; import com.seata.demo.vo.AccountDO; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class AccountController { @Autowired private AccountService accountService; @RequestMapping("/save") public int save(@RequestBody AccountDO accountVo) { return accountService.save(accountVo); } @RequestMapping("/transfer") @GlobalTransactional public boolean transfer(@RequestBody TransferVo transferVo) { // 转出方 accountService.add(new AccountDO(transferVo.getFrom(), -1 * transferVo.getAccount())); // 转入方 accountService.add(new AccountDO(transferVo.getTo(), transferVo.getAccount())); // int i = 1 / 0; // 人为制造异常 return true; } } @Data /** * 转账参数VO */ class TransferVo { private String from; private String to; double account; } |
编写application.yml配置文件:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
server: port: 8888 spring: application: name: seata-validate-demo datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://127.0.0.1:3306/seata_demo cloud: nacos: # 需要依赖注册中心进行远程调用 discovery: enabled: true namespace: public server-addr: 127.0.0.1:8848 seata: enabled: true application-id: seata-server # Seata 事务组编号,用于 TC 集群名 tx-service-group: default service: vgroupMapping: default: default seata-validate-demo-group: default config: type: nacos nacos: server-addr: 127.0.0.1:8848 # 对应nacos地址 group: SEATA_GROUP data-id: seataServer.properties # 对应【分布式事务命名空间 ruoyi_seata】下创建ruoyiSeata.properties配置文件 namespace: # 对应分布式事务命名空间 registry: type: nacos nacos: application: seata-server # 对应`conf/application.yml`中registry.nacos.application server-addr: 127.0.0.1:8848 # 对应nacos地址 cluster: default group: SEATA_GROUP namespace: # 对应分布式事务命名空间 |
项目整体结构参考如下:

完成编码和配置以后,启动项目,进行转账接口测试,请求之前:

进行请求:
此时查看应用后端,发现应用报错:
|
1 2 3 4 |
java.lang.ArrayIndexOutOfBoundsException: 0 at io.seata.core.rpc.processor.client.ClientOnResponseProcessor.process(ClientOnResponseProcessor.java:103) ~[seata-all-2.0.0.jar:2.0.0] at io.seata.core.rpc.netty.AbstractNettyRemoting.processMessage(AbstractNettyRemoting.java:306) ~[seata-all-2.0.0.jar:2.0.0] at io.seata.core.rpc.netty.AbstractNettyRemotingClient$ClientHandler.channelRead(AbstractNettyRemotingClient.java:411) [seata-all-2.0.0.jar:2.0.0] |
通过查看seata-server端,也发现报错日志:
|
1 2 3 4 5 6 7 |
21:55:25.432 ERROR --- [rverHandlerThread_1_6_500] [pc.netty.AbstractNettyRemoting] [bda$processMessage$2] [192.168.239.1:8091:6161483039933980673] : 0104 ==> java.lang.NoClassDefFoundError: Could not initialize class io.seata.server.cluster.raft.RaftServerFactory$SingletonHandler at io.seata.server.cluster.raft.RaftServerFactory.getInstance(RaftServerFactory.java:78) ~[classes!/:2.0.0] at io.seata.server.session.GlobalSession.removeBranch(GlobalSession.java:352) ~[classes!/:2.0.0] at io.seata.server.session.SessionHelper.removeBranch(SessionHelper.java:429) ~[classes!/:2.0.0] at io.seata.server.coordinator.DefaultCore.lambda$doGlobalRollback$3(DefaultCore.java:325) ~[classes!/:2.0.0] |
如果出现上面错误,需要在nacos配置中心上seata配置增加如下配置:

|
1 |
server.enableParallelRequestHandle=false |
增加配置以后,重启seata和应用,重新请求:

请求完成以后:

可以看到此时转账成功了。
我们人为制造一下异常,查看一下异常情况下能不能正常回滚,修改代码:
|
1 2 3 4 5 6 7 8 9 10 |
@RequestMapping("/transfer") @GlobalTransactional public boolean transfer(@RequestBody TransferVo transferVo) { // 转出方 accountService.add(new AccountDO(transferVo.getFrom(), -1 * transferVo.getAccount())); // 转入方 accountService.add(new AccountDO(transferVo.getTo(), transferVo.getAccount())); int i = 1 / 0; // 人为制造异常 return true; } |
重启应用,进行请求:

查看数据库,数据没有发生变化:

可以看到,此时seata进行分布式事务管理是成功的,你也可以在方法执行完成之前进行断点,然后查看客户端undo_log,此时可以看到SQL镜像数据哦。

本次主要是介绍seata 2.0版本的AT模式入门,后续有时间会给大家介绍TCC模式、Saga模式以及seata的底层原理,和我们自定义的一些扩展,欢迎大家点点关注。