seata实现2PC事务

  1. 业务说明
    本示例通过Seata中间件实现分布式事务,模拟三个账户的转账交易过程。
    两个账户在三个不同的银行(张三在bank1、李四在bank2),bank1和bank2是两个微服务。交易过程是,张三
    给李四转账指定金额。
  2. 交互流程如下
    1、请求bank1进行转账,传入转账金额。
    2、bank1减少转账金额,调用bank2,传入转账金额。
  3. 创建数据库
    seata-bank1库:
    --账户表
    CREATE TABLE `account_info` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `account_name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '户主姓名',
      `account_no` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '银行卡号',
      `account_password` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '帐户密码',
      `account_balance` double DEFAULT NULL COMMENT '帐户余额',
      PRIMARY KEY (`id`) USING BTREE
    );
    
    insert  into `account_info`(`id`,`account_name`,`account_no`,`account_password`,`account_balance`) values (2,'张三','1',NULL,1000);
    
    --seata框架需要使用的表
    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=167 DEFAULT CHARSET=utf8;

    seata-bank2库:
    --账户表
    CREATE TABLE `account_info` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `account_name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '户主姓名',
      `account_no` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '银行卡号',
      `account_password` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '帐户密码',
      `account_balance` double DEFAULT NULL COMMENT '帐户余额',
      PRIMARY KEY (`id`) USING BTREE
    );
    
    insert  into `account_info`(`id`,`account_name`,`account_no`,`account_password`,`account_balance`) values (3,'李四的账户','2',NULL,0);
    
    --seata框架需要使用的表
    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=167 DEFAULT CHARSET=utf8;

     

  4. 启动TC(事务协调器)
    1) 下载seata服务器(版本是0.7.1)

    2)  微服务相关版本说明
     

    3) 最后直接解压seata服务器通过下面的命令启动(进入到D:\seata-server-0.7.1\bin目录下)
        命令:seata-server.bat -p 8888 -m file
  5. 下面我们创建了下面的代码结构

    seata-bank1    ---->服务1
    seata-bank2    ---->服务2
  6. 环境配置
    nacos1.3.1
    maven3.5.2
    seata0.7.1
    springboot2.1.12.RELEASE
    springcloud alibaba2.1.0.RELEASE
    springcloud Greenwich.SR6
  7. seata-parent代码结构

    pom.xml文件
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.seata</groupId>
        <artifactId>seata-parent</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>../seata-bank1</module>
            <module>../seata-bank2</module>
            <module>../seata-common</module>
        </modules>
    
        <!--继承一个父模块,然后再引入相应的依赖  -->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.12.RELEASE</version>
            <!--relativePath是可选的,maven会首先搜索这个地址,在搜索本地远程repositories之前  -->
            <relativePath />
        </parent>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java-version>1.8</java-version>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
        </properties>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-cloud-alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.SR6</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    </project>

     

  8. seata-common代码结构

    pom.xml内容:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>seata-parent</artifactId>
            <groupId>com.seata</groupId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../seata-parent/pom.xml</relativePath>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>seata-common</artifactId>
        <properties>
            <commons.lang.version>2.6</commons.lang.version>
            <mybatis-plus.version>3.2.0</mybatis-plus.version>
            <lombok.version>1.18.8</lombok.version>
            <mysql.version>8.0.17</mysql.version>
            <druid.version>1.1.21</druid.version>
        </properties>
        <dependencies>
            <!--引入mybatis-plus依赖-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--引入lombok依赖-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
                <version>${commons.lang.version}</version>
            </dependency>
            <!--引入mysql依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!--引入nacos的注册中心依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--引入nacos的配置中心依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
                <exclusions> <!-- 去除springboot默认的logback配置-->
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- alibaba的druid数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions> <!-- 去除springboot默认的logback配置-->
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-logging</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- 加入log4j2配置 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j2</artifactId>
            </dependency>
        </dependencies>
    </project>

     

  9. seata-bank1代码结构


    application.yml内容:
    spring:
      application:
          name: seata-bank1
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
        alibaba:
          seata:
            tx-service-group: seata-bank1_tx_group
      datasource:
        url: jdbc:mysql://xxx.xx.xxx.xx:3306/seata-bank1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xx
        password: xxx
        driver-class-name: com.mysql.cj.jdbc.Driver
        initial-size: 5 #初始连接数
        min-idle: 10 #最小连接池数量
        max-active: 20 #最大连接池数量
        max-wait: 60000 #配置获取连接等待超时的时间
        time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
        max-evictable-idle-time-millis: 900000 #配置一个连接在池中最大生存的时间,单位是毫秒
        validation-query: SELECT 1 FROM DUAL #配置检测连接是否有效
        test-while-idle: true #建议配置为true,不影响性能,并且保证安全性申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        test-on-borrow: false #这里建议配置为TRUE,防止取到的连接不可用
        test-on-return: false #归还连接时执行validationQuery检测连接是否有效
        web-stat-filter:
          enabled: true
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #设置不统计哪些URL
        stat-view-servlet:
          enabled: true
          login-username: #控制台管理用户名和密码
          login-password:
          allow: "" #默认允许所有访问
          reset-enable: false #禁止手动重置监控数据
          deny: #IP黑名单(共同存在时,deny优先于allow)
          url-pattern: /druid/* #servlet访问路径设置
        filter:
          stat:
            #慢SQL记录
            slow-sql-millis: 1000
            merge-sql: true
            log-slow-sql: true #是否展示慢查询sql
            enabled: true
            db-type: mysql
        filters: stat,wall,slf4j #设置使用哪些插件 stat是统计,wall是SQL防火墙,防SQL注入的,log4j是用来输出统计数据的
        pool-prepared-statements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        max-pool-prepared-statement-per-connection-size: 0
    server:
      port: 7001
      servlet:
        context-path: /seata-bank1
    mybatis-plus:
      type-aliases-package: com.seata.bank1
      mapper-locations: classpath:/mappers/**/*.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    feign:
      sentinel:
        enabled: true #服务降级必须加上这个才生效
      httpclient:
        connection-timeout: 60000
        enabled: true
      client:
        config:
          default:
            connectTimeout: 60000
            readTimeout: 60000
            loggerLevel: FULL
    logging:
      config: classpath:log4j2-dev.xml
      level:
        com.seata.bank1: debug

    bootstrap.properties内容:

    spring.application.name=seata-bank1
    spring.cloud.nacos.config.namespace=c987cc45-4012-4ee2-9757-3662e830b3cd
    spring.cloud.nacos.config.server-addr=localhost:8848
    spring.cloud.nacos.config.ext-config[0].data-id=seata-bank1-dev.yml
    spring.cloud.nacos.config.ext-config[0].group=dev
    spring.cloud.nacos.config.ext-config[0].refresh=true

    把压缩包里面的2个配置文件拿到resource下面,修改配置

    file.conf内容:


    registry.conf内容:


    SeataBank1Application内容:

    package com.seata.bank1;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableDiscoveryClient
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) //取消数据源的自动创建。使用我们自己配置的seata代理的数据源
    @MapperScan(value = "com.seata.bank1.mapper")
    @EnableFeignClients
    public class SeataBank1Application {
    
        public static void main(String[] args) {
    
            SpringApplication.run(SeataBank1Application.class,args);
    
            System.out.println("===================SeataBank1Application started============================");
    
        }
    
    }
    

    配置类DatabaseConfiguration.java内容:

    package com.seata.bank1.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import io.seata.rm.datasource.DataSourceProxy;
    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;
    
    @Configuration
    public class DatabaseConfiguration {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DruidDataSource  druidDataSource() {
            DruidDataSource druidDataSource = new DruidDataSource();
            return druidDataSource;
        }
    
        @Primary
        @Bean
        public DataSourceProxy dataSource(DruidDataSource druidDataSource){
            return new DataSourceProxy(druidDataSource);
        }
    }
    

    bank1实现如下功能:
        1) 
    张三账户减少金额,开启全局事务。
        2) 远程调用bank2向李四转账。
    web层:

    package com.seata.bank1.controller;
    
    import com.seata.bank1.service.AccountInfoService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Slf4j
    public class AccountInfoController {
    
        @Autowired
        private AccountInfoService accountInfoService;
    
        @RequestMapping("transferAccount")
        public void transferAccount(Double amount){
            log.info("transferAccount amount is {}",amount);
            accountInfoService.tranferAmount(amount);
            log.info("完成转账流程。。。。。。。。。。");
        }
    }
    

    service层:

    package com.seata.bank1.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.seata.bank1.feign.Bank2Feign;
    import com.seata.bank1.mapper.AccountInfoMapper;
    import com.seata.bank1.model.AccountInfo;
    import com.seata.bank1.service.AccountInfoService;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    
    @Service
    @Slf4j
    public class AccountInfoServiceImpl extends ServiceImpl<AccountInfoMapper, AccountInfo> implements AccountInfoService {
    
    
        @Resource
        private Bank2Feign bank2Feign;
    
        @Resource
        private AccountInfoMapper accountInfoMapper;
    
        @GlobalTransactional//开启全局事务
        @Transactional//开启本地事务
        @Override
        public void tranferAmount(Double amount) {
            //张三先减去金额
            accountInfoMapper.updateAccountBalanceByParams(amount);
            //为李四增加金额
            String transfer = bank2Feign.tranferAmountToBank2(amount);
            if("fallback".equals(transfer)){
                //调用李四微服务异常
                throw new RuntimeException("调用李四微服务异常");
            }
            if(amount == 2){
                //人为制造异常
                throw new RuntimeException("bank1 make exception..");
            }
        }
    }
    

    dao层:

    package com.seata.bank1.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.seata.bank1.model.AccountInfo;
    import org.springframework.web.bind.annotation.RequestParam;
    
    public interface AccountInfoMapper extends BaseMapper<AccountInfo> {
        void updateAccountBalanceByParams(@RequestParam("amount") Double amount);
    }
    

    FeignClient:

    package com.seata.bank1.feign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @FeignClient(value = "seata-bank2",fallback = Bank2FeignFallback.class)
    public interface Bank2Feign {
    
        @RequestMapping(value = "/seata-bank2/tranferAmountToBank2",method = RequestMethod.POST)
        String tranferAmountToBank2(@RequestParam("amount") Double amount);
    }
    

    降级服务:

    package com.seata.bank1.feign;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    @Component
    @Slf4j
    public class Bank2FeignFallback implements Bank2Feign{
        @Override
        public String tranferAmountToBank2(Double amount) {
            log.info("tranferAmountToBank2 出错,进行服务降级。。。。。");
            return "fallback";
        }
    }
    

    AccountInfoMapperxml:

    <?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.seata.bank1.mapper.AccountInfoMapper">
    
        <update id="updateAccountBalanceByParams">
            update account_info set account_balance = account_balance-#{amount} where id = '2'
        </update>
    </mapper>

     

  10. seata-bank2代码结构

    同样把压缩包里面的2个配置文件拿到resource下面,修改配置

    file.conf内容:


    registry.conf内容:


    application.yml内容:
    spring:
      application:
          name: seata-bank2
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
        alibaba:
          seata:
            tx-service-group: seata-bank2_tx_group
      datasource:
        url: jdbc:mysql://xxx.xx.xxx.xx:3306/seata-bank2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: xx
        password: xxxx
        driver-class-name: com.mysql.cj.jdbc.Driver
        initial-size: 5 #初始连接数
        min-idle: 10 #最小连接池数量
        max-active: 20 #最大连接池数量
        max-wait: 60000 #配置获取连接等待超时的时间
        time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
        max-evictable-idle-time-millis: 900000 #配置一个连接在池中最大生存的时间,单位是毫秒
        validation-query: SELECT 1 FROM DUAL #配置检测连接是否有效
        test-while-idle: true #建议配置为true,不影响性能,并且保证安全性申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        test-on-borrow: false #这里建议配置为TRUE,防止取到的连接不可用
        test-on-return: false #归还连接时执行validationQuery检测连接是否有效
        web-stat-filter:
          enabled: true
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #设置不统计哪些URL
        stat-view-servlet:
          enabled: true
          login-username: #控制台管理用户名和密码
          login-password:
          allow: "" #默认允许所有访问
          reset-enable: false #禁止手动重置监控数据
          deny: #IP黑名单(共同存在时,deny优先于allow)
          url-pattern: /druid/* #servlet访问路径设置
        filter:
          stat:
            #慢SQL记录
            slow-sql-millis: 1000
            merge-sql: true
            log-slow-sql: true #是否展示慢查询sql
            enabled: true
            db-type: mysql
        filters: stat,wall,slf4j #设置使用哪些插件 stat是统计,wall是SQL防火墙,防SQL注入的,log4j是用来输出统计数据的
        pool-prepared-statements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        max-pool-prepared-statement-per-connection-size: 0
    server:
      port: 7002
      servlet:
        context-path: /seata-bank2
    mybatis-plus:
      type-aliases-package: com.seata.bank2
      mapper-locations: classpath:/mappers/**/*.xml

    bootstrap.properties:

    spring.application.name=seata-bank2
    spring.cloud.nacos.config.namespace=cd1f38e6-43d4-45ac-9b19-76435345c0e5
    spring.cloud.nacos.config.server-addr=localhost:8848
    spring.cloud.nacos.config.ext-config[0].data-id=seata-bank2-dev.yml
    spring.cloud.nacos.config.ext-config[0].group=dev
    spring.cloud.nacos.config.ext-config[0].refresh=true

    配置类:

    package com.seata.bank2.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import io.seata.rm.datasource.DataSourceProxy;
    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;
    
    @Configuration
    public class DatabaseConfiguration {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DruidDataSource druidDataSource(){
            return new DruidDataSource();
        }
    
        @Primary
        @Bean
        public DataSourceProxy dataSource(DruidDataSource druidDataSource){
            return new DataSourceProxy(druidDataSource);
        }
    
    }
    

    启动类:

    package com.seata.bank2;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @EnableDiscoveryClient
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) //取消数据源的自动创建。使用我们自己配置的seata代理的数据源
    @MapperScan(value = "com.seata.bank2.mapper")
    public class SeataBank2Application {
    
        public static void main(String[] args) {
    
            SpringApplication.run(SeataBank2Application.class,args);
    
            System.out.println("====================SeataBank2Application started==========================");
    
        }
    
    }
    

    web层:

    package com.seata.bank2.controller;
    
    import com.seata.bank2.service.AccountInfo2Service;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Slf4j
    public class AccountInfo2Controller {
    
        @Autowired
        private AccountInfo2Service accountInfo2Service;
    
        @RequestMapping("tranferAmountToBank2")
        public String tranferAmountToBank2(@RequestParam("amount") Double amount){
            log.info("tranferAmountToBank2 amount is {}",amount);
            accountInfo2Service.tranferAmountToBank2(amount);
            log.info("完成转账流程。。。。。。。。。。");
            return "bank2"+amount;
        }
    }
    

    service层:

    package com.seata.bank2.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.seata.bank2.mapper.AccountInfoMapper;
    import com.seata.bank2.model.AccountInfo;
    import com.seata.bank2.service.AccountInfo2Service;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    
    @Service
    public class AccountInfo2ServiceImpl extends ServiceImpl<AccountInfoMapper, AccountInfo> implements AccountInfo2Service {
    
        @Resource
        private AccountInfoMapper accountInfoMapper;
    
        @Transactional//开启本地事务
        @Override
        public void tranferAmountToBank2(Double amount) {
            accountInfoMapper.tranferAmountToBank2(amount);
        }
    }
    

    dao层:

    package com.seata.bank2.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.seata.bank2.model.AccountInfo;
    import org.springframework.web.bind.annotation.RequestParam;
    
    public interface AccountInfoMapper extends BaseMapper<AccountInfo> {
        void tranferAmountToBank2(@RequestParam("amount") Double amount);
    }
    

    AccountInfoMapper.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.seata.bank2.mapper.AccountInfoMapper">
    
        <update id="tranferAmountToBank2">
            update account_info set account_balance = account_balance+#{amount} where id = '3'
        </update>
    </mapper>

     

  11. seata分布式事务的流程

    要点说明:

    1、每个RM使用DataSourceProxy连接数据库,其目的是使用ConnectionProxy,使用数据源和数据连接代理的目

    的就是在第一阶段将undo_log和业务数据放在一个本地事务提交,这样就保存了只要有业务操作就一定有

    undo_log。

    2、在第一阶段undo_log中存放了数据修改前和修改后的值,为事务回滚作好准备,所以第一阶段完成就已经将分

    支事务提交,也就释放了锁资源。

    3、TM开启全局事务开始,将XID全局事务id放在事务上下文中,通过feign调用也将XID传入下游分支事务,每个

    分支事务将自己的Branch ID分支事务ID与XID关联。

    4、第二阶段全局事务提交,TC会通知各各分支参与者提交分支事务,在第一阶段就已经提交了分支事务,这里各

    各参与者只需要删除undo_log即可,并且可以异步执行,第二阶段很快可以完成。

    5、第二阶段全局事务回滚,TC会通知各各分支参与者回滚分支事务,通过 XID 和 Branch ID 找到相应的回滚日

    志,通过回滚日志生成反向的 SQL 并执行,以完成分支事务回滚到之前的状态,如果回滚失败则会重试回滚操

    作。

  12. 测试场景
    原数据


    1)seata-bank1通过张三用户向李四账户转1元钱,成功场景
           


    2)seata-bank1通过张三用户向李四账户转1元钱,将seata-bank2关掉,调用李四服务的时候挂掉



    可以发现事务进行了回滚,张三的钱成功回滚
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值