seata入门

7 篇文章 0 订阅

什么是分布式系统cap定理?

一致性:如果我部署了多个相同的服务,用户访问所有的服务,拿到的数据都是一样的;

可用性:比如说我们设置一个合理时间为1秒,如果用户访问这个接口超过一秒,那就不是可用性;

分区容忍度(容错性):当出现通信故障时,能正常提供服务;

一致性分类;

强一致性:数据必须完全一致,中间过程不可见,同步完成,意思就是张三和李四转账,钱必须完全一致,其他人在进行给他俩转账的时候阻塞,等待他俩完成才能进行;

弱一致性:允许部分一致性,中间过程可见,异步完成,意思是系统给A,B,C三个服务异步发送消息,我访问A服务和B服务看到的数据是一样的,看到C服务的数据是不一样的;

最终一致性:过一段时间后,保证数据完全一致;

分布式解决方案JTA(XA)

准备阶段:业务处理,但不会提交数据,当处理完成之后,进入提交阶段;

提交阶段:如果准备阶段出现错误,那么提交阶段立即回滚,如果没有出现错误,那么提交成功;

JTA就是采用2个阶段进行事物的处理;

XA是一个分布式事物的规范,采用两阶段方案(Pre,Commit);

TCC分布式事物解决方案?

TCC是try-尝试,confirm-确认,cancel-取消;

try尝试阶段,对资源进行锁定;

confirm确认阶段,对资源进行确认,完成操作;

cancel取消阶段,对资源进行还原,取消操作;

try尝试阶段:创建预增字段,写入预增数据;

confirm确认阶段:把预增数据,写入真实的字段中;

cancel取消阶段:当出现报错,把真实字段的数据进行还原;

seata分布式事物解决方案?

seata为用户提供了AT,TCC,SAGA 三种事物模式

seata的AT事物模式?

TC:事物协调者,维护全局和分支事物的状态,驱动全局事物提交和回滚;

TM:事物管理器,用于定义全局事物的范围,开始全局事物提交或者回滚全局事物;

RM:资源管理器,用于管理分支事物处理的资源,与TC进行数据的交换,并且报告分支事物的状态,并驱动分支事物提交或者回滚;

TM事物管理器,管理会员采购这个核心方法,这个方法里面包含订单服务的创建订单方法,积分服务的增加积分方法,库存服务的减少库存方法;

RM资源管理器对应不同的服务,订单服务,创建订单,积分服务,增加积分,库存服务,减少库存;每个服务都有自己的RM资源管理器

Seata-server会提供一个TC的事物协调者;

TM开启一个TC的全局事物请求,TM中的方法里面,调用不同服务的的方法,方法执行成功后,每个服务添加一份数据到undo_log中,

然后RM会向TC上报一个处理结果,通知TC我这个RM处理成功了。

当TM管理的核心方法,全部处理完成之后,向TC发送一个全局提交的请求,一旦TC收到这个请求,表示所以操作都完成了,通知RM,并把undo_log删除;

当TM管理的核心方法,有一个报错了,向TC发送一个全局回滚的请求,然后TC通知每一个RM,进行回滚数据;

接下来下载

https://github.com/seata/seata/releases/download/v1.6.1/seata-server-1.6.1.zip

seata的控制台

http://localhost:7091/

账号seata,密码seata

把注册中心和配置中心都改成nacos

#  Copyright 1999-2019 Seata.io Group.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  store:
    # support: file 、 db 、 redis
    mode: file
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

然后启动seata

在nacos就能看到注册上了

进入这个目录

在mysql创建一个数据库名字叫seata,导入这个mysql.sql

进入下面的目录,编辑config.txt文件

修改成你自己的ip

store.mode=db,数据库改成你的

这几个地方给他加上双引号,要不然执行失败

然后 在这个目录右键 git bash here ,前提是你先安装好git

执行下面的命令

sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos

可以看到nacos已经有配置信息了

然后我们在启动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 DEFAULT CHARSET=utf8;

在订单数据库创建订单表

CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

在库存数据库创建库存表

CREATE TABLE `kucun` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT NULL COMMENT '库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入库存数据

INSERT INTO `kucun` (`id`, `name`, `count`) VALUES ('2', '北京库存', '10');

接下来创建springcloud项目

创建库存项目

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>kc2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>kc2</name>
    <description>Demo project for Spring Boot</description>


    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--这个web包一定要有,否则项目启动不起来-->
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <!-- 添加MyBatisPlus的依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- MySQL数据 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- druid  连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--这个包解决tc向rm发送回滚指令-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>


    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.example.kc2.Kc2Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

这是seata的核心包

package com.example.kc2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class Kc2Application {

    public static void main(String[] args) {
        SpringApplication.run(Kc2Application.class, args);
    }

}
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
server:
  port: 8089
spring:
  application:
    name: kc2
  cloud:
    nacos:
      discovery:
        namespace: public
        password: nacos
        server-addr: localhost:8848
        username: nacos
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.23.131:13309/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root


seata:
  enabled: true
  #应用id
  application-id: ${spring.application.name}
  #事物组 service.vgroupMapping.default_tx_group
  tx-service-group: default_tx_group
  #这里设置false 使用自定义的代理数据源配置,这样就可以解决mapper找不到的问题
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  service:
    vgroupMapping:
      default_tx_group: default


logging:
  level:
    io:
      seata: debug
package com.example.kc2.service;

public interface KuCunService {

    public void update();
}
package com.example.kc2.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.kc2.entity.KuCun;
import com.example.kc2.mapper.KuCunMapper;
import com.example.kc2.service.KuCunService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class KuCunServiceImpl implements KuCunService {

    @Autowired
    private KuCunMapper kuCunMapper;


    @Override
    public void update() {
        KuCun kuCun=kuCunMapper.selectById(2);
        kuCun.setCount(10);
        kuCunMapper.updateById(kuCun);
    }
}
package com.example.kc2.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.kc2.entity.KuCun;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface KuCunMapper extends BaseMapper<KuCun> {


}
package com.example.kc2.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("kucun")
public class KuCun {
    @TableId(type = IdType.AUTO)
    private Integer id;

    private String name;

    private Integer count;
}
package com.example.kc2.controller;

import com.example.kc2.service.KuCunService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class KuCunController {

    @Autowired
    private KuCunService kuCunService;

    @GetMapping("/update")
    public void update(){
        kuCunService.update();
    }
}
package com.example.kc2.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan({"com.example.kc2.mapper"})
public class MyBatisConfig {
}
package com.example.kc2.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
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 DataSourceProxyConfig {



    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

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

    //使用io.seata.rm.datasource.DataSourceProxy
    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

接下来创建订单项目

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>order</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>order</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--这个web包一定要有,否则项目启动不起来-->
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <!-- 添加MyBatisPlus的依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- MySQL数据 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- druid  连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--这个包解决tc向rm发送回滚指令-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.example.order.OrderApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
server:
  port: 8088
spring:
  application:
    name: order
  cloud:
    nacos:
      discovery:
        namespace: public
        password: nacos
        server-addr: localhost:8848
        username: nacos
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.23.131:13310/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root

seata:
  enabled: true
  #应用id
  application-id: ${spring.application.name}
  #事物组 service.vgroupMapping.default_tx_group
  tx-service-group: default_tx_group
  #这里设置false 使用自定义的代理数据源配置,这样就可以解决mapper找不到的问题
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  service:
    vgroupMapping:
      default_tx_group: default


logging:
  level:
    io:
      seata: debug
package com.example.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}
package com.example.order.service;

public interface OrderService {

    public void addOrder();
}
package com.example.order.service.impl;

import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;


    @Override
    public void addOrder() {
        Order order=new Order();
        order.setOrderName("订单名称");
        orderMapper.insert(order);


    }
}
package com.example.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.order.entity.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {


}
package com.example.order.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("t_order")
public class Order {
    @TableId(type = IdType.AUTO)
    private Integer id;

    private String orderName;
}
package com.example.order.controller;

import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/addOrder")
    public void addOrder(){
        orderService.addOrder();
    }
}
package com.example.order.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan({"com.example.order.mapper"})
public class MyBatisConfig {
}
package com.example.order.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
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 DataSourceProxyConfig {



    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

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

    //使用io.seata.rm.datasource.DataSourceProxy
    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

接下来我们在创建业务项目

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>yewu</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>yewu</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--这个web包一定要有,否则项目启动不起来-->
        <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>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>



        <!-- 添加MyBatisPlus的依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- MySQL数据 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- druid  连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--和其他服务进行通信-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--这个包解决tc向rm发送回滚指令-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.example.yewu.YewuApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
server:
  port: 8087
spring:
  application:
    name: yewu
  cloud:
    nacos:
      discovery:
        namespace: public
        password: nacos
        server-addr: localhost:8848
        username: nacos
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.23.131:13306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root

seata:
  enabled: true
  #应用id
  application-id: ${spring.application.name}
  #事物组 service.vgroupMapping.default_tx_group
  tx-service-group: default_tx_group
  #设置为false,使用自定义的代理数据源,解决mapper找不到的问题
  enable-auto-data-source-proxy: false
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
  service:
    vgroupMapping:
      default_tx_group: default

#打印seata日志
logging:
  level:
    io:
      seata: debug
package com.example.yewu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class YewuApplication {

    public static void main(String[] args) {
        SpringApplication.run(YewuApplication.class, args);
    }

}
package com.example.yewu.service;

public interface TestService {

    public void test();
}
package com.example.yewu.service.impl;

import com.example.yewu.feign.KcFeign;
import com.example.yewu.feign.OrderFeign;
import com.example.yewu.service.TestService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestServiceImpl implements TestService {


    @Autowired
    private KcFeign kcFeign;
    @Autowired
    private OrderFeign orderFeign;

    /**
     * 开启seata的全局事物
     * name:事物名
     * timeoutMills:超时时间 超过时间回滚
     *
     * @param
     * @return
     * @throws Exception
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public void test() {
        //调用库存服务
        kcFeign.update();
        //调用订单服务
        orderFeign.addOrder();
        //模拟报错
        //int i=1/0;
       // throw new RuntimeException("aaaaaaaaaa");

    }
}
package com.example.yewu.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

//名称就是在nacos注册的微服务的名字
@FeignClient(value = "order")
public interface OrderFeign {


    @GetMapping("/addOrder")
    public void addOrder();
}
package com.example.yewu.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

//名称就是在nacos注册的微服务的名字
@FeignClient(value = "kc2")
public interface KcFeign {


    @GetMapping("/update")
    public void update();
}
package com.example.yewu.controller;

import com.example.yewu.feign.KcFeign;
import com.example.yewu.feign.OrderFeign;
import com.example.yewu.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping("/test")
    public void test(){
        testService.test();
    }
}
package com.example.yewu.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan({"com.example.yewu.mapper"})
public class MyBatisConfig {
}
package com.example.yewu.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
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 DataSourceProxyConfig {



    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

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

    //使用io.seata.rm.datasource.DataSourceProxy
    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

接下来我们演示下正常的效果

库存是100

订单是空

启动3个项目和seata,nacos

当RM(资源管理器)启动成功之后,就是我们的库存,订单项目

会在TC(事物协调者),就是seata-server上面 显示,TM注册成功,和RM注册成功

当项目关闭之后,会从通道中移除

如果seata的cmd长期没有反应,那么按下回车

接下来我们测试一把成功的

访问http://localhost:8087/test

可以看到处理成功

我们可以在TC看到,开启一个新的全局事物,注册2个分支,最后提交全局事物

全局事物就是靠这个注解,默认超时时间是60秒

@GlobalTransactional(rollbackFor = Exception.class)

我们在在数据还原,并把这里的注释打开测试一下回滚

可以在TC看到,开启一个全局事物,注册2个分支,回滚2个分支,回滚全局事物

可以看到,库存和订单已经回滚成功

之前在事物回滚的时候发现失效了,我在就库存和订单搜索rollback

发现是空的,并且xid也是null

那么我就在pom里面加入了这个包,最终解决事物回滚失效,导致tc无法发送给rm指令

如果遇到了其他的问题,请到官网去看相应的问题

http://seata.io/zh-cn/docs/overview/faq.html

AT模式执行解析;

1.业务方法准备运行-TM向TC发起全局事物,生成xid(全局锁);

2.库存方法-写表,undo_log纪录回滚日志,生成branch_id(分支id,每一个分支id都不一样),通知TC操作结果;

3.订单方法-写表,undo_log纪录回滚日志,生成branch_id,通知TC操作结果;

4.业务方法-执行成功,TM通知TC全局提交;

5.TC通知所有RM提交成功,删除undo_log回滚日志;

6.业务方法-执行失败,TM通知TC全局回滚;

7.TC通知所有RM进行回滚,RM通过undo_log进行回滚数据,恢复后,删除undo_log;

这样就保证了我们的最终一致性;

接下来我们看下undo_log到底长什么样,把下面的代码设置一下超时时间

我们把undo_log数据拿出来

可以看到开启的数据是库存100

修改过后的数据是10,通过这些数据,我们就知道是怎么恢复了

使用场景:在老项目,新项目都可以使用,一般都是使用的AT模式

接下来看下TCC事物模式

一阶段prepare(准备)行为:调用自己的准备方法;

二阶段commit(提交/确认)行为:调用自己的提交方法;

二阶段 rollback(回滚)行为:调用自己的回滚方法;

@LocalTCC标识为本地模式,本地调用事物,非远程调用,只要实现了两阶段提交方法,

都必须在该接口上面加这个注解;

TCC模式是不用undo_log的;

然后我们对sql进行改造,加入一些预增字段

在库存表加入冻结库存字段

CREATE TABLE `kucun` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT NULL COMMENT '库存',
  `dong_jie` int(11) DEFAULT NULL COMMENT '冻结库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

在订单表加入状态字段

CREATE TABLE `t_order` (
  `status` int(11) DEFAULT NULL,
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;

接下来对订单项目改造

package com.example.order.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

//本地tcc
@LocalTCC
public interface OrderService {

    public void addOrder();

    /**
     * 准备阶段
     * TwoPhaseBusinessAction 两阶段业务操作
     * commitMethod 提交确认方法
     * rollbackMethod 回滚方法
     * name tcc的bean名称,全局唯一
     * BusinessActionContextParameter 可以把参数传递给commitMethod,rollbackMethod方法,
     * 通过BusinessActionContext来获取
     * @param
     * @return
     * @throws Exception
     */
    @TwoPhaseBusinessAction(name = "prepare",commitMethod = "commit",rollbackMethod = "rollback")
    boolean prepare(@BusinessActionContextParameter(paramName = "id")Integer id);
    /**
     *
     * 提交阶段
     * @param
     * @return
     * @throws Exception
     */
    boolean commit(BusinessActionContext context);

    /**
     *
     * 回滚阶段
     * @param
     * @return
     * @throws Exception
     */
    boolean rollback(BusinessActionContext context);

}
package com.example.order.service.impl;

import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;


    @Override
    public void addOrder() {
        Order order=new Order();
        order.setOrderName("订单名称");
        orderMapper.insert(order);
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean prepare(Integer id) {
        Order bean = orderMapper.selectById(id);
        if(bean!=null){
            //防止重复数据
            return true;
        }
        Order order=new Order();
        order.setId(id);
        order.setOrderName("订单名称");
        //0 准备阶段  1 完成
        order.setStatus(0);
        orderMapper.insert(order);
        return true;
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean commit(BusinessActionContext context) {
        int id=Integer.parseInt(context.getActionContext("id").toString());
        Order order = orderMapper.selectById(id);
        if(order==null){
            //防止空提交
            return true;
        }
        //0 准备阶段  1 完成
        order.setStatus(1);
        orderMapper.updateById(order);
        log.info("事物xid:"+context.getXid()+",提交成功");
        return true;
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean rollback(BusinessActionContext context) {
        int id=Integer.parseInt(context.getActionContext("id").toString());
        Order order = orderMapper.selectById(id);
        if(order==null){
            //防止空回滚
            return true;
        }
        //回滚数据
        orderMapper.deleteById(id);
        log.info("事物xid:"+context.getXid()+",回滚成功");
        return true;
    }
}
package com.example.order.controller;

import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/addOrder")
    public void addOrder(){
        orderService.prepare(10);
    }
}

接下来对库存项目进行改造

package com.example.kc2.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

//本地TCC
@LocalTCC
public interface KuCunService {

    public void update();


    /**
     * 准备阶段
     * TwoPhaseBusinessAction 两阶段业务操作
     * commitMethod 提交确认方法
     * rollbackMethod 回滚方法
     * name tcc的bean名称,全局唯一
     * BusinessActionContextParameter 可以把参数传递给commitMethod,rollbackMethod方法,
     * 通过BusinessActionContext来获取
     * @param
     * @return
     * @throws Exception
     */
    @TwoPhaseBusinessAction(name = "prepare",commitMethod = "commit",rollbackMethod = "rollback")
    boolean prepare(@BusinessActionContextParameter(paramName = "id")Integer id);
    /**
     *
     * 提交阶段
     * @param
     * @return
     * @throws Exception
     */
    boolean commit(BusinessActionContext context);

    /**
     *
     * 回滚阶段
     * @param
     * @return
     * @throws Exception
     */
    boolean rollback(BusinessActionContext context);
}
package com.example.kc2.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.kc2.entity.KuCun;
import com.example.kc2.mapper.KuCunMapper;
import com.example.kc2.service.KuCunService;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
public class KuCunServiceImpl implements KuCunService {

    @Autowired
    private KuCunMapper kuCunMapper;


    @Override
    public void update() {
        KuCun kuCun=kuCunMapper.selectById(2);
        kuCun.setCount(10);
        kuCunMapper.updateById(kuCun);
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean prepare(Integer id) {
        KuCun kuCun = kuCunMapper.selectById(id);
        if(kuCun==null){
            //防止空数据
            return true;
        }
        //把数据先放到冻结库存里面
        kuCun.setDongJie(10);
        kuCunMapper.updateById(kuCun);
        return true;
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean commit(BusinessActionContext context) {
        int id=Integer.parseInt(context.getActionContext("id").toString());
        KuCun kuCun = kuCunMapper.selectById(id);
        if(kuCun==null){
            //防止空数据
            return true;
        }
        //把冻结的库存放入真实库存
        kuCun.setCount(kuCun.getDongJie());
        //把冻结库存清空
        kuCun.setDongJie(0);
        kuCunMapper.updateById(kuCun);
        log.info("事物xid:"+context.getXid()+",提交成功");
        return true;
    }

    //事物的传播行为,每次都创建一个新的事物
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public boolean rollback(BusinessActionContext context) {
        int id=Integer.parseInt(context.getActionContext("id").toString());
        KuCun kuCun = kuCunMapper.selectById(id);
        if(kuCun==null){
            //防止空数据
            return true;
        }
        //重置库存
        kuCun.setCount(100);
        //把冻结库存清空
        kuCun.setDongJie(0);
        kuCunMapper.updateById(kuCun);
        log.info("事物xid:"+context.getXid()+",回滚成功");
        return true;
    }
}
package com.example.kc2.controller;

import com.example.kc2.service.KuCunService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class KuCunController {

    @Autowired
    private KuCunService kuCunService;

    @GetMapping("/update")
    public void update(){
        kuCunService.prepare(2);
    }
}

接下来在访问

http://localhost:8087/test

先做一些报错的数据,我们看下效果,可以看到数据都回滚了

在库存,订单项目可以看到

接下来我们弄1条成功的数据

可以看到,数据已经发送了变化

在库存,订单项目可以看到

在准备阶段,我们把数据放入预增字段;

在提交阶段,才把数据放入真正的字段;

在回滚的时候,才把数据进行恢复;

使用场景:在新项目中使用;

接下来我们看下Saga事物模式

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

说白了就是我创建订单的时候,要有一个取消订单的方法;

当前支付成功的时候,要有一个取消支付的方法;

一个成功的方法,一个失败的方法;

这个模式只做下了解,真正用的还是AT模式;

使用场景:业务流程长,业务流程多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值