一、SpringCloudAlibaba分布式事务实战Seata

一.分布式事务概述

1、分布式事务基础理论


1.什么是分布式事务

  • 针对于单数据库的事务我们叫做本地事务/传统事务,在分布式环境中一个请求可能涉及到多个数据库的写操作俗称多数据源,保证多数据源的一致性事务叫做分布式事务

2.为什么需要分布式事务

  • 系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出
  • 一个下单请求同时涉及到订单库,优惠券库,库存库的写操作,需要保证三个库写操作的一致性,就要用到分布式事务即:分布式事务就是要解决一个请求同时对多个数据库写操作的一致性
  • 注意:微服务拆分原则,尽量让大部分操作都不要跨微服务操作,也就是跨库,分布式事务比本地事务耗费的资源更多

202208211210974.png

3.分布式事务解决方案

2PC、3PC、Seata、TCC、 可靠消息最终一致性(依靠MQ)、最大努力通知(调用其他系统)

2、2PC方案


1.什么是2PC

  • 2PC即两阶段提交协议,是将整个事务流程分为两个阶段,2是指两个阶段, P是指准备阶段, C是指提交阶段
    • P:准备阶段/Prepare phase
    • C:提交阶段/commit phase
  • 在第一阶段/准备阶段,事务参与者发送准备请求,大家都返回OK状态,那么就进入第二阶段,提交事务,如果在第一阶段有任何一个参与者没有OK,那么事务协调器通知其他所有事务参与者/资源回滚事务
  • 2PC常见的落地实现有:XA、Seata等等
  • 二阶段能保证分布式事务的原子性,但是也有一些明显的缺陷
    • 在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好
    • 单节点故障,如果协调器挂了,参与者会阻塞,比如在第二阶段,如果事务协调器宕机,参与者没办法回复信息,长时间处于事务资源锁定,造成阻塞,事务操作是要加锁的
    • 在第二阶段,如果在事务协调器发出“commit”执行后宕机,一部和参与者收到了消息提交了事务,而一部分没有消息没法做出事务提交操作,这样就出现了数据不一致
    • 在第二阶段,如果事务事务协调器发出“commit”指令后宕机,收到“commmit”指令的参与者也宕机了,那么事务最终变成了什么效果,提交了还是没提交,就不清楚了
  • 2PC成功的情况

202208211212259.png

  • 2PC失败的情况

202208211213617.png

2.基于XA的2PC

  • 2PC的传统方案是在数据库层面实现的如:Oracle、MySQL都支持2PC协议,为了统一标准,减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,国际开放标准组织Open Group定义了分布式事务处理模型DTP
  • DTP模型的三个角色
    • AP(Application Program) :即应用程序,可以理解为使用DTP分布式事务的程序
    • TM(Transaction Manager) :事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM
    • RM(Resource Manager) :即资源管理器,可以理解为事务的参与者, 一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务
  • 全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务
  • DTP模型定义TM和RM之间通讯的接口规范叫XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现2PC又称为XA方案
  • 以上三个角色之间的交互方式如下:
    • TM向AP提供应用程序编程接口,AP通过TM提交及回滚事务
    • TM交易中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等
  • 基于XA模式的2PC方法的缺点
    • 资源锁定时间长
    • 资源释放需要等到二阶段提交完成后才会释放,性能差

202208211431393.png

3、基于Seata的2pc


1.Seata概述

  • Seata是由阿里中间件团队发起的开源项目Fescar,后更名为Seata,它是一个是开源的分布式事务框架
  • 传统2PC的问题在Seata中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件,主要优点是性能较好,且不长时间占用连接资源,它以高效并且对业务零侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案
    • Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚,相当于是一个软件需要单独部署
    • Transaction Manager ™:事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令
    • Resource Manager (RM):资源管理器控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚

2.正常执行流程

  • 用户服务的TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  • 用户服务的RM向TC注册分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入XID对应全局事务的管辖
  • 用户服务执行分支事务,向用户表插入一条记录
  • 逻辑执行到远程调用积分服务时(XID在微服务调用链路的,上下文中传播),积分服务的RM向TC注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入XID对应全局事务的管辖
  • 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务

202208211445036.png

3.异常执行流程

  • 前面2个步骤都跟上面一样,在第 3 步的时候,Order在可能因为某种原因本地分支事务提交失败了,那么RM会向TC上报一个失败的事务状态
  • 在第 4 步,这个时候代码回到Business,这时TM事务管理器会向TC事务协调者发起全局事务回滚请求,TC向RM事务分支发起事务回滚请求,RM(Order, Account)收到回滚指令,然后会解析undo log,指向反向操作,把数据还原到修改之前,删除undolog,提交本地事务

202208211447210.png

4.Seata和XA的差别

  • 架构层次方面,传统2PC方案的RM实际上是在数据库层,RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以Jar包的形式作为中间件层部署在应用程序这一侧的
  • 两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成才释放,而Seata的做法是在Phase1就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率

4、TCC方案


1.什么是TCC

  • TCC是基于补偿型事务AP系统的一种实现,具有弱一致性
  • 名词解释
    • 补偿理解:按照事先预定的方案去执行,如果失败了就走补偿方案,类似于撤销、还原
    • AP系统:在分布式系统中,CAP性质对应了分布式系统的三个指标,但是一个分布式系统不能同时满足三个指标
      • 一致性/Consistency:对于客户端的每次读操作,要么读到的是最新的数据,要么读取失败,换句话说,一致性是站在分布式系统的角度,对访问本系统的客户端的一种承诺:要么我给您返回一个错误,要么我给你返回绝对一致的最新数据,不难看出,其强调的是数据正确
      • 可用性/Availability:任何客户端的请求都能得到响应数据,不会出现响应错误,换句话说,可用性是站在分布式系统的角度,对访问本系统的客户的另一种承诺:我一定会给您返回数据,不会给你返回错误,但不保证数据最新,强调的是不出错
      • 分区容忍性/Partition tolerance:由于分布式系统通过网络进行通信,网络是不可靠的,当任意数量的消息丢失或延迟到达时,系统仍会继续提供服务,不会挂掉,换句话说,分区容忍性是站在分布式系统的角度,对访问本系统的客户端的再一种承诺:我会一直运行,不管我的内部出现何种数据同步问题,强调的是不挂掉
    • 弱一致性:弱一致性也叫做最终一致性,数据最终会保持同步

202208211459480.png

2.常见TCC方案

  • 目前市面上的TCC框架众多比如下面这几种
  • tcc-transaction
  • Hmily

3.TCC优缺点

  • 优点 : 异步执行效率高
    • TCC能够对分布式事务中的各个资源进行分别锁定, 分别提交与释放,例如:假设有AB两个操作, 假设A操作耗时短, 那么A就能较快的完成自身的try-confirm-cancel流程, 释放资源,无需等待B操作,如果事后出现问题, 追加执行补偿性事务即可
    • TCC是绑定在各个子业务上的(除了cancle中的全局回滚操作), 也就是各服务之间可以在一定程度上”异步并行”执行
  • 缺点
    • 对应用的侵入性强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高,实现难度较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略,为了满足一致性的要求,confirm和cancel接口必须实现幂等,多次操作数据库数据是一致的

4.可靠消息最终一致性

1.什么是可靠消息最终一致性
  • 可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致
  • 此方案是利用消息中间件完成
    • 用户服务-注册用户
    • 积分服务-赠送积分

202208211502084.png

2.面临的问题
  • 最终一致性要解决三个问题:本地事务和消息发送的原子性,接收消息的可靠性,消息重复消费问题(幂等)
    • 本地事务和消息发送原子性
      • 本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息
      • 实现本地事务和消息发送的原子性,要么都成功,要么都失败
      • 本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题
    • 事务参与方接收消息可靠性ack机制,消息确认机制
      • 事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息
    • 消息重复消息问题幂等性校验
      • 由于网络的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费
      • 要解决消息重复消费的问题就要实现事务参与方的方法幂等性
3.本地消息表方案
  • 本地消息表这个方案最初是eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除
  • 优点:简单
  • 缺点:要创建很多本地消息表
4.RocketMQ方案

202208211505657.png

  • RocketMQ是一个来自阿里巴巴的分布式消息中间件,于2012年开源,并在2017年正式成为Apache顶级项目,据了解,包括阿里云上的消息产品以及收购的子公司在内,阿里集团的消息产品全线都运行在RocketMQ上
  • 在最近几年的双十一大促中,RocketMQ都有抢眼表现,Apache RocketMQ 4.3之后的版本正式支持事务消息,为分布式事务实现提供了便利性支持
  • RocketMQ事务消息设计则主要是为了解决Producer端的消息发送与本地事务执行的原子性问题
  • RocketMQ的设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力
  • RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性,在RocketMQ 4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题
  • 执行步骤如下:
    • MQ发送方发送远程事务消息到MQ Server
    • MQ Server给予响应, 表明事务消息已成功到达MQ Server
    • MQ发送方Commit本地事务
    • 若本地事务Commit成功, 则通知MQ Server允许对应事务消息被消费; 若本地事务失败, 则通知MQ Server对应事务消息应被丢弃
    • 若MQ发送方超时未对MQ Server作出本地事务执行状态的反馈, 那么需要MQ Server向MQ发送方主动回查事务状态, 以决定事务消息是否能被消费
    • 当得知本地事务执行成功时, MQ Server允许MQ订阅方消费本条事务消息
  • 需要额外说明的一点,就是事务消息投递到MQ订阅方后,并不一定能够成功执行,需要MQ订阅方主动给予消费反馈(ack)
  • 如果MQ订阅方执行远程事务成功,则给予消费成功的ack,那么MQ Server可以安全将事务消息移除
  • 如果执行失败, MQ Server需要对消息重新投递, 直至消费成功

202208211507790.png

5.最大努力通知

1.什么是最大努力通知
  • 最大努力通知服务表示在不影响主业务的情况下,尽可能地确保数据的一致性,它需要开发人员根据业务来指定通知规则,在满足通知规则的前提下,尽可能的确保数据的一致,以达到最大努力的目的
  • 目标:接口提供方/通知方通过一定的机制最大努力将业务处理结果通知到接口调用方/接口调用方
    • 有一定的消息重复通知机制
    • 因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知:10s 1min 10min 1h 5h 1d
    • 消息校对机制
    • 如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求
      • 消息处理方需要保证幂等性
  • 场景:支付宝结果通知

202208211510987.png

2.最大努力通知和最终一致性的区别
  • 可靠消息最终一致性
    • 系统A本地事务执行成功,通知系统B处理任务,通常通过MQ实现。一般适用于平台内部,对一致性要求相对较高(微服务的2个子系统之间)
  • 最大努力通知
    • 所谓最大努力通知就是系统A用最大努力通知系统B,能不能成功,不做完全保证,如果没通知到位,系统B可以主动来调用系统A的接口查询结果状态。一般适用于跨平台业务,或对接了上方平台的业务场景(支付结果通知)

6.技术选型

1.2PC方案选择
  • 2PC最大的诟病是一个阻塞协议。RM在执行分支事务后需要等待TM的决定,此时服务会阻塞并锁定资源。由于其阻塞机制和最差时间复杂度高,因此,这种设计不能适应随着事务涉及的服务数量增加而扩展的需要,很难用于并发较高以及子事务生命周期较长(long-running transactions)的分布式服务中
  • 但是2pc是强一致性的,如果业务对两个数据库数据的一致性要求较高,需要选择2PC方案,Stata是一个不错的选择,它屏蔽了传统2PC的诟病
2.TCC方案选择
  • 如果拿TCC事务的处理流程与2PC两阶段提交做比较, 2PC通常都是在跨库的DB层面,而TCC则在应用层面的处理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能。而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、 cancel三个操作。 此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。典型的使用场景:登录送优惠券等
3.最终一致性
  • 可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作,避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。典型的使用场景:注册送积分,登录送优惠券等
4.最大努力通知
  • 最大努力通知是分布式事务中要求最低的一种,适用于一些最终一致性时间敏感度低的业务 ;允许发起通知方处理业务失败,在接收通知方收到通知后积极进行失败处理,无论发起通知方如何处理结果都会不影响到接收通知方的后续处理;发起通知方需提供查询执行情况接口,用于接收通知方校对结果。典型的使用 场景:银行通知、支付结果通知等。 如果有跨平台的业务接入,可以选择该方案

二.注册集成Seata

1、安装SeataServer


1.下载

2.配置文件修改

  • 如果内存不够时会启动失败,可以修改配置文件解决

2、主业务方集成Seata


1.导入依赖

<!--seata依赖,TM+RM集成包-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<!--seata也需要连接数据库,进行undolog表的日志存储,所以需要用到连接池,我们已经导入过了-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>

2.yml配置

seata:
  enableAutoDataSourceProxy: false #关闭DataSource代理的自动配置,我们要手动配置
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: fsp_tx_group #这里和file.conf中事务组名一样

3.拷贝配置文件

1.resources目录下file.conf
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  vgroupMapping.fsp_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}
2.resources目录下registry.conf
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  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、springCloudConfig
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

4.DataSource配置

1.排除DataSource自动配置
  • @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})
2.MybatisPlus版本
  • 把DataSource交给Seata代理
package com.cyx.yxcc.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
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.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
 * 数据源代理
 */
@Configuration
public class DataSourceConfiguration {

    //mapper.xml路径
    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;

    //手动配置bean
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        //处理MybatisPlus
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSourceProxy);
        factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //事务管理工厂
        factory.setTransactionFactory(new SpringManagedTransactionFactory());
        return factory;
    }

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }

}

3.Mybatis版本
  • 把DataSource交给Seata代理
package com.cyx.yxcc.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;

//使用seata对DataSource进行代理
@Configuration
public class DataSourceProxyConfig {

    //mapper.xml路径
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    //手动配置bean
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public SqlSessionFactory sessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceProxy);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //事务管理工厂
        sessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sessionFactoryBean.getObject();
    }

    @Bean
    public DataSourceProxy dataSource() {
        return new DataSourceProxy(druidDataSource());
    }
}

5.业务方法

  • 方法上贴 : @GlobalTransactional(rollbackFor = Exception.class) 开启Seata全局事务

6.注释事务开启注解

  • 注意:MybatisPlusConfig中的@EnableTransactionManagement注解不能加, 也不需要加@Transactional注解
package com.cyx.yxcc.config;

import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
// mapper接口扫描
@MapperScan("com.cyx.yxcc.mapper")
// 开启事务管理,让我们在service中可以使用事务注解,springboot项目默认开启
// @EnableTransactionManagement
public class MybatisPlusConfig {

    /**
     * 分页插件配置对象,Mybatis-plus需要此配置对象
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

7.undo_log表

  • 数据库中创建表,涉及到事务的表都需要添加undolog
-- 注意此处0.3.0+ 增加唯一索引 ux_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;

3、事务参与者


  • 编写方式和主业务方集成一致,但是不用加@GlobalTransactional(rollbackFor = Exception.class) 注解

4、测试


  • 在注册业务最后手动制造错误,查看uaa服务是否保存成功,需要保存失败
  • 那么就取消错误,正常执行业务,保存成功即为集成完毕

image.png
image.png
image.png
image.png
image.png
image.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值