Seata整合SpringBoot和Mybatis

一、背景

上一节中,我们学习了Seata的集群部署,在这篇文章中,我们使用SpringBoot整合Seata实现分布式事务功能,此处使用的是SeataAT模式。

二、实现功能

我们存在2个服务 账户服务 account-service订单服务 order-service,在订单服务中调用 账户服务。

订单服务中调用账户服务是通过 RestTemplate来实现的。

测试场景:

1、账户服务正常,订单服务正常,结果:账户服务正常扣款,产生订单。

2、账户服务正常,订单服务正常,在整个分布式事务中发生了异常,结果: 账户服务没有扣款,没有产生订单。

三、每个服务使用到的技术

1、账户服务

SpringBoot、Seata、Mybatis、nacos、druid

2、订单服务

SpringBoot、Seata、Mybatis、nacos、Hikari

其中 SpringBoot 整合 Seata 是通过 seata-spring-boot-starter 这个来实现的,不使用 seata-all来实现。

四、服务实现

1、账户服务实现

账户服务,提供一个简单的扣除账户余额的功能,比较简单。

注意项:

1、开启自动数据源代理。

2、引入druid,不需要自动配置数据源。

3、注意事务分组

1、引入jar包

此处只引入几个 核心的 包,其余的包没有列在下方,比如mybatis等,注意和seata整合使用的是seata-spring-boot-starter

<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>

2、项目配置

3、建表语句

create database seata_account;
use seata_account;
create table account(
id int unsigned auto_increment primary key comment '主键',
name varchar(20) comment '用户名',
balance bigint comment '账户余额,单位分'
) engine=InnoDB comment '账户表';
insert into account(id,name,balance) values (1,'张三',100000);
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 COMMENT ='AT transaction mode undo table';

每个业务库必须存在一张 undo_log

2、订单服务实现

提供一个接口,实现产生订单,扣除账户余额。

注意事项:

1、订单服务 关闭默认的数据源代理,自己配置数据源代理。

2、使用 Hikari数据源来实现,因为使用的是 AT模式,所以需要使用 DataSourceProxy 来代理数据源。

3、订单服务调用账户服务是采用的 RestTemplate,因此需要手动配置RestTemplate的拦截器,实现xid的传输。

4、在seata1.4.2中存在一个bug,如果业务表中数据类型是datetime类型,可能undolog无法序列化成功,可以采用timestamp或别的方式来处理。

5、业务库中需要存在 undo_log 表。

1、引入jar包

此处不引入 druid,注意和seata整合使用的是seata-spring-boot-starter

<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>

2、项目配置

3、配置数据源代理

package com.huan.seata.config;

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /**
* @author huan.fu 2021/9/24 - 上午10:34
*/
@Configuration
public class DataSourceConfig { @Autowired
private DataSourceProperties dataSourceProperties; @Bean
public DataSource dataSourceProxy() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(dataSourceProperties.getUrl());
hikariDataSource.setUsername(dataSourceProperties.getUsername());
hikariDataSource.setPassword(dataSourceProperties.getPassword());
hikariDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
return new DataSourceProxy(hikariDataSource);
}
}

AT模式种,数据源代理一定要是 DataSourceProxy这个。

4、配置RestTemplate传递xid

5、@GlobalTransactional分布式事务

@Service
@RequiredArgsConstructor
@Slf4j
public class BusinessServiceImpl implements BusinessService { private final OrderService orderService;
private final RestTemplate restTemplate; @Override
@GlobalTransactional(rollbackFor = Exception.class)
public void createAccountOrder(Integer accountId, Long amount, boolean hasException) {
System.out.println("createAccountOrder:" + RootContext.getXID());
// 1、远程扣减账户余额
remoteDebit(accountId, amount); // 2、下订单
orderService.createOrder(accountId, amount); if (hasException) {
throw new RuntimeException("发生了异常,分布式事物需要会滚");
}
} private void remoteDebit(Integer accountId, Long amount) {
String url = "http://localhost:50001/account/debit?id=" + accountId + "&amount=" + amount;
String result = restTemplate.getForObject(url, String.class);
log.info("远程扣减库存结果:[{}]", result);
}
}

3、事务分组需要和配置中心对应上

此处以订单服务来演示,如何和配置中心对应上的。

每个服务的事务分组可能不一样,但是需要和配置中心对应上。
比如:
order-service 中的配置分组为:seata.tx-service-group=tx_order_service_group
配置中心必须存在 service.vgroupMapping.tx_order_service_group=default 配置项,default是集群,是服务端配置文件中指定的

五、演示

1、没有发生异常

访问:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=false

正常创建订单,和扣除余额。

2、发生异常

访问: http://localhost:50002/createOrder?accountId=1&amount=10&hasException=true

不产生订单,不扣除余额。

六、可能遇到的问题

1.Nacos 作为 Seata 配置中心时,项目启动报错找不到服务。如何排查,如何处理?

A: 异常:io.seata.common.exception.FrameworkException: can not register RM,err:can not connect to services-server.

  1. 查看nacos配置列表,seata配置是否已经导入成功
  2. 查看nacos服务列表,serverAddr是否已经注册成功
  3. 检查client端的registry.conf里面的namespace,registry.nacos.namespace和config.nacos.namespace填入nacos的命名空间ID,默认"",server端和client端对应,namespace 为public是nacos的一个保留控件,如果您需要创建自己的namespace,最好不要和public重名,以一个实际业务场景有具体语义的名字来命名
  4. nacos上服务列表,serverAddr地址对应ip地址应为seata启动指定ip地址,如:sh seata-server.sh -p 8091 -h 122.51.204.197 -m file
  5. 查看seata/conf/nacos-config.txt 事务分组service.vgroupMapping.trade_group=default配置与项目分组配置名称是否一致
  6. telnet ip 端口 查看端口是都开放,以及防火墙状态

2、使用 AT 模式需要的注意事项有哪些 ?

  1. 必须使用代理数据源,有 3 种形式可以代理数据源:
  • 依赖 seata-spring-boot-starter 时,自动代理数据源,无需额外处理。
  • 依赖 seata-all 时,使用 @EnableAutoDataSourceProxy (since 1.1.0) 注解,注解参数可选择 jdk 代理或者 cglib 代理。
  • 依赖 seata-all 时,也可以手动使用 DatasourceProxy 来包装 DataSource。
  1. 配置 GlobalTransactionScanner,使用 seata-all 时需要手动配置,使用 seata-spring-boot-starter 时无需额外处理。
  2. 业务表中必须包含单列主键,若存在复合主键,请参考问题 13 。
  3. 每个业务库中必须包含 undo_log 表,若与分库分表组件联用,分库不分表。
  4. 跨微服务链路的事务需要对相应 RPC 框架支持,目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。
  5. 目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL和 TiDB。
  6. 使用注解开启分布式事务时,若默认服务 provider 端加入 consumer 端的事务,provider 可不标注注解。但是,provider 同样需要相应的依赖和配置,仅可省略注解。
  7. 使用注解开启分布式事务时,若要求事务回滚,必须将异常抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer 判断再抛出异常。

3、AT 模式和 Spring @Transactional 注解连用时需要注意什么 ?

@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务,大家常用的是与本地事务结合。当与本地事务结合时,@Transactional和@GlobalTransaction连用,@Transactional 只能位于标注在@GlobalTransaction的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。

4、数据库开启自动更新时间戳导致脏数据无法回滚

由于业务提交,seata记录当前镜像后,数据库又进行了一次时间戳的更新,导致镜像校验不通过。

**解决方案1: **关闭数据库的时间戳自动更新。数据的时间戳更新,如修改、创建时间由代码层面去维护,比如MybatisPlus就能做自动填充。

解决方案2: update语句别把没更新的字段也放入更新语句。

5、Seata 使用注册中心注册的地址有什么限制?

Seata 注册中心不能注册 0.0.0.0 或 127.0.0.1 的地址,当自动注册为上述地址时可以通过启动参数 -h 或容器环境变量SEATA_IP来指定。当和业务服务处于不同的网络时注册地址可以指定为 NAT_IP或公网IP,但需要保证注册中心的健康检查探活是通畅的。

以上的几个问题,来自seata官网 : http://seata.io/zh-cn/docs/overview/faq.html

七、代码地址

代码地址:https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-mybatis

Seata整合SpringBoot和Mybatis的更多相关文章

  1. eclipse下整合springboot和mybatis

    1.新建maven项目 先新建一个maven项目,勾选上creat a simple project,填写groupid,artifactid 2.建立项目结构 3.添加依赖 <parent&g ...

  2. intellij idea 整合springboot和mybatis

    参考: http://blog.csdn.net/winter_chen001/article/details/77249029

  3. SpringBoot和Mybatis的整合

    这里介绍两种整合SpringBoot和Mybatis的模式,分别是“全注解版” 和 “注解xml合并版”. 前期准备开发环境 开发工具:IDEAJDK:1.8技术:SpringBoot.Maven.M ...

  4. 基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis

    SpringBoot 整合 Mybatis 有两种常用的方式,一种就是我们常见的 xml 的方式 ,还有一种是全注解的方式.我觉得这两者没有谁比谁好,在 SQL 语句不太长的情况下,我觉得全注解的方式 ...

  5. Springboot与Mybatis整合

    最近自己用springboot和mybatis做了整合,记录一下: 1.先导入用到的jar包 <dependency> <groupId>org.springframework ...

  6. SpringBoot与Mybatis整合方式01(源码分析)

    前言:入职新公司,SpringBoot和Mybatis都被封装了一次,光用而不知道原理实在受不了,于是开始恶补源码,由于刚开始比较浅,存属娱乐,大神勿喷. 就如网上的流传的SpringBoot与Myb ...

  7. springboot+springmvc+mybatis项目整合

    介绍: 上篇给大家介绍了ssm多模块项目的搭建,在搭建过程中spring整合springmvc和mybatis时会有很多的东西需要我们进行配置,这样不仅浪费了时间,也比较容易出错,由于这样问题的产生, ...

  8. SpringBoot系列——MyBatis整合

    前言 MyBatis官网:http://www.mybatis.org/mybatis-3/zh/index.html 本文记录springboot与mybatis的整合实例:1.以注解方式:2.手写 ...

  9. SpringBoot Mybatis整合(注解版),SpringBoot集成Mybatis(注解版)

    SpringBoot Mybatis整合(注解版),SpringBoot集成Mybatis(注解版) ================================ ©Copyright 蕃薯耀 2 ...

随机推荐

  1. 图像处理之Canny边缘检测(一)

    一:历史 Canny边缘检测算法是1986年有John F. Canny开发出来一种基于图像梯度计算的边缘 检测算法,同时Canny本人对计算图像边缘提取学科的发展也是做出了很多的贡献.尽 管至今已经 ...

  2. 完蛋,公司被一条 update 语句干趴了!

    大家好,我是小林. 昨晚在群划水的时候,看到有位读者说了这么一件事. 在这里插入图片描述 大概就是,在线上执行一条 update 语句修改数据库数据的时候,where 条件没有带上索引,导致业务直接崩 ...

  3. MySQL实战45讲(21--25)-笔记

    21 | 为什么我只改一行的语句,锁这么多? 加锁规则里面:包含了两个"原则".两个"优化"和一个"bug". 原则 1:加锁的基本单位是 ...

  4. Apache配置与应用

    目录: 一.基于域名的虚拟主机 二.基于IP地址的虚拟主机 三.基于端口的虚拟主机 四.Apache连接保持 五.构建Web虚拟目录与用户授权限制 六.Apache日志分割 七.AWStats 分析系 ...

  5. 用Java实现红黑树

    红黑树是众多"平衡的"搜索树模式中的一种,在最坏情况下,它相关操作的时间复杂度为O(log n). 1.红黑树的属性 红黑树是一种二分查找树,与普通的二分查找树不同的一点是,红黑树 ...

  6. 理解ASP.NET Core - [04] Host

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...

  7. Java 语法学习2

    Java基础语法二 类型转换 public class demo03 { public static void main(String[] args) { int i=128; byte a=(byt ...

  8. 浅谈VMware的NAT模式

    什么是NAT模式?理论化的措辞我就不说了,我将结合本人平时的经验来简单的说明一下NAT模式,以及配置NAT模式时遇到的问题. 大家都知道,我们的电脑要想联网,需要与交换机连接,假设交换机的网关为192 ...

  9. 【MySQL】MySQL基础(SQL语句、约束、数据类型)

    数据库的基本概念 什么是数据库? 用于存储和管理数据的仓库 英文单词为:DataBase,简称DB 数据库的好处? 可以持久化存储数据 方便存储和管理数据 使用了统一的方式操作数据库 -- SQL 常 ...

  10. POJ2251——Dungeon Master(三维BFS)

    和迷宫问题区别不大,相比于POJ1321的棋盘问题,这里的BFS是三维的,即从4个方向变为6个方向. 用上队列的进出操作较为轻松. #include<iostream> #include& ...