Seata分布式事物案例及详解

spring cloud alibaba Seata分布式事物 详解

安装seata

官网: https://seata.apache.org/zh-cn/download/seata-server

下载后直接解压 进到bin目录 输入cmd

启动命令:seata-server.bat

进入localhost:7091 默认账户跟密码都为seata

seate分布式事务案例

  1. 创建数据库
xml 复制代码
CREATE DATABASE IF NOT EXISTS `storage_db`;
USE `storage_db`;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `commodity_code` varchar(255) DEFAULT NULL,
    `count` int(11) DEFAULT 0,
    PRIMARY KEY (`id`),
    UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ('P0001', 100);
INSERT INTO storage_tbl (commodity_code, count) VALUES ('B1234', 10);
DROP TABLE IF EXISTS `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;

CREATE DATABASE IF NOT EXISTS `order_db`;
USE `order_db`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `user_id` varchar(255) DEFAULT NULL,
    `commodity_code` varchar(255) DEFAULT NULL,
    `count` int(11) DEFAULT 0,
    `money` int(11) DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `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;

CREATE DATABASE IF NOT EXISTS `account_db`;
USE `account_db`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `user_id` varchar(255) DEFAULT NULL,
    `money` int(11) DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO account_tbl (user_id, money) VALUES ('1', 10000);
DROP TABLE IF EXISTS `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;
  1. 导入依赖
java 复制代码
 <dependencies>
        <dependency>
            <groupId>com.nie</groupId>
            <artifactId>model</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>2023.0.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>annotationProcessor</scope>
        </dependency>

    </dependencies>
  1. 创建四个微服务项目

seata-account

启动类

java 复制代码
package com.nie.account;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@EnableTransactionManagement
@MapperScan("com.nie.account.mapper")
@EnableDiscoveryClient
@SpringBootApplication
public class SeataAccountMainApplication {

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

实体类

java 复制代码
package com.nie.account.bean;

import lombok.Data;

import java.io.Serializable;

/**
 * 
 * @TableName account_tbl
 */
@Data
public class AccountTbl implements Serializable {

    private Integer id;

    private String userId;

    private Integer money;

}

控制层

java 复制代码
package com.nie.account.controller;

import com.nie.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AccountRestController {


    @Autowired
    AccountService accountService;
    /**
     * 扣减账户余额
     * @return
     */
    @GetMapping("/debit")
    public String debit(@RequestParam("userId") String userId,
                        @RequestParam("money") int money){
        accountService.debit(userId, money);
        return "account debit success";
    }
}

持久层

java 复制代码
package com.nie.account.mapper;

import com.nie.account.bean.AccountTbl;


public interface AccountTblMapper {

    int deleteByPrimaryKey(Long id);

    int insert(AccountTbl record);

    int insertSelective(AccountTbl record);

    AccountTbl selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(AccountTbl record);

    int updateByPrimaryKey(AccountTbl record);

    void debit(String userId, int money);
}
java 复制代码
<?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.nie.account.mapper.AccountTblMapper">

    <resultMap id="BaseResultMap" type="com.nie.account.bean.AccountTbl">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="userId" column="user_id" jdbcType="VARCHAR"/>
            <result property="money" column="money" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,user_id,money
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from account_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from account_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.nie.account.bean.AccountTbl" useGeneratedKeys="true">
        insert into account_tbl
        ( id,user_id,money
        )
        values (#{id,jdbcType=INTEGER},#{userId,jdbcType=VARCHAR},#{money,jdbcType=INTEGER}
        )
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.nie.account.bean.AccountTbl" useGeneratedKeys="true">
        insert into account_tbl
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">id,</if>
                <if test="userId != null">user_id,</if>
                <if test="money != null">money,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">#{id,jdbcType=INTEGER},</if>
                <if test="userId != null">#{userId,jdbcType=VARCHAR},</if>
                <if test="money != null">#{money,jdbcType=INTEGER},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.nie.account.bean.AccountTbl">
        update account_tbl
        <set>
                <if test="userId != null">
                    user_id = #{userId,jdbcType=VARCHAR},
                </if>
                <if test="money != null">
                    money = #{money,jdbcType=INTEGER},
                </if>
        </set>
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.nie.account.bean.AccountTbl">
        update account_tbl
        set 
            user_id =  #{userId,jdbcType=VARCHAR},
            money =  #{money,jdbcType=INTEGER}
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <update id="debit">
        update account_tbl
        set money = money - #{money,jdbcType=INTEGER}
        where user_id = #{userId,jdbcType=VARCHAR}
    </update>
</mapper>

业务层

java 复制代码
package com.nie.account.service;

public interface AccountService {

    /**
     * 从用户账户中扣减
     * @param userId  用户id
     * @param money   扣减金额
     */
    void debit(String userId, int money);
}
java 复制代码
package com.nie.account.service.impl;

import com.nie.account.mapper.AccountTblMapper;
import com.nie.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountTblMapper accountTblMapper;

    @Transactional  //本地事务
    @Override
    public void debit(String userId, int money) {
        // 扣减账户余额
        accountTblMapper.debit(userId,money);
    }
}

配置application.yml

xml 复制代码
spring:
  application:
    name: seata-account
  datasource:
    url: jdbc:mysql://localhost:3306/account_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
server:
  port: 10010

mybatis:
  mapper-locations: classpath:mapper/*.xml
#seata:
#  data-source-proxy-mode: XA

配置seata地址

xml 复制代码
 service {
   #transaction service group mapping
   vgroupMapping.default_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
 }

seata-business

启动类

java 复制代码
package com.nie.business;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;


@EnableFeignClients(basePackages = "com.nie.business.feign")
@EnableDiscoveryClient
@SpringBootApplication
public class SeataBusinessMainApplication {

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

控制层

java 复制代码
package com.nie.business.controller;

import com.nie.business.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PurchaseRestController {

    @Autowired
    BusinessService businessService;


    /**
     * 购买
     * @param userId 用户ID
     * @param commodityCode 商品编码
     * @param orderCount 数量
     * @return
     */
    @GetMapping("/purchase")
    public String purchase(@RequestParam("userId") String userId,
                           @RequestParam("commodityCode") String commodityCode,
                           @RequestParam("count") int orderCount){
        businessService.purchase(userId, commodityCode, orderCount);
        return "business purchase success";
    }
}

远程调用

java 复制代码
package com.nie.business.feign;


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

@FeignClient(value = "seata-order")
public interface OrderFeignClient {


    /**
     * 创建订单
     * @param userId
     * @param commodityCode
     * @param orderCount
     * @return
     */
    @GetMapping("/create")
    String create(@RequestParam("userId") String userId,
                         @RequestParam("commodityCode") String commodityCode,
                         @RequestParam("count") int orderCount);
}
java 复制代码
package com.nie.business.feign;


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

@FeignClient(value = "seata-storage")
public interface StorageFeignClient {

    /**
     * 扣减库存
     * @param commodityCode
     * @param count
     * @return
     */
    @GetMapping("/deduct")
    String deduct(@RequestParam("commodityCode") String commodityCode,
                         @RequestParam("count") Integer count);
}

业务层

java 复制代码
package com.nie.business.service;

public interface BusinessService {

    /**
     * 采购
     * @param userId            用户id
     * @param commodityCode     商品编号
     * @param orderCount        购买数量
     */
    void purchase(String userId, String commodityCode, int orderCount);
}
java 复制代码
package com.nie.business.service.impl;

import com.nie.business.feign.OrderFeignClient;
import com.nie.business.feign.StorageFeignClient;
import com.nie.business.service.BusinessService;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class BusinessServiceImpl implements BusinessService {

    @Autowired
    StorageFeignClient storageFeignClient;

    @Autowired
    OrderFeignClient orderFeignClient;

	
    @GlobalTransactional
    @Override
    public void purchase(String userId, String commodityCode, int orderCount) {
        //1. 扣减库存
        storageFeignClient.deduct(commodityCode, orderCount);

        //2. 创建订单
        orderFeignClient.create(userId, commodityCode, orderCount);

    }
}

配置application.yml

xml 复制代码
spring:
  application:
    name: seata-business
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
server:
  port: 11000

配置seata地址

xml 复制代码
 service {
   #transaction service group mapping
   vgroupMapping.default_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
 }

seata-order

启动类

java 复制代码
package com.nie.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.transaction.annotation.EnableTransactionManagement;



@EnableFeignClients(basePackages = "com.nie.order.feign")
@EnableTransactionManagement
@MapperScan("com.nie.order.mapper")
@EnableDiscoveryClient
@SpringBootApplication
public class SeataOrderMainApplication {

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


}

实体类

java 复制代码
package com.nie.order.bean;

import java.io.Serializable;
import lombok.Data;

/**
 * @TableName order_tbl
 */
@Data
public class OrderTbl implements Serializable {
    private Integer id;

    private String userId;

    private String commodityCode;

    private Integer count;

    private Integer money;

    private static final long serialVersionUID = 1L;
}

控制层

java 复制代码
package com.nie.order.controller;


import com.nie.order.bean.OrderTbl;
import com.nie.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderRestController {

    @Autowired
    OrderService orderService;


    /**
     * 创建订单
     * @param userId
     * @param commodityCode
     * @param orderCount
     * @return
     */
    @GetMapping("/create")
    public String create(@RequestParam("userId") String userId,
                         @RequestParam("commodityCode") String commodityCode,
                         @RequestParam("count") int orderCount)
    {
        OrderTbl tbl = orderService.create(userId, commodityCode, orderCount);
        return "order create success = 订单id:【"+tbl.getId()+"】";
    }

}

业务层

java 复制代码
package com.nie.order.service;

import com.nie.order.bean.OrderTbl;

public interface OrderService {
    /**
     * 创建订单
     * @param userId    用户id
     * @param commodityCode  商品编码
     * @param orderCount  商品数量
     */
    OrderTbl create(String userId, String commodityCode, int orderCount);
}
java 复制代码
package com.nie.order.service.impl;

import com.nie.order.bean.OrderTbl;
import com.nie.order.feign.AccountFeignClient;
import com.nie.order.mapper.OrderTblMapper;
import com.nie.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    OrderTblMapper orderTblMapper;



    @Autowired
    AccountFeignClient accountFeignClient;

    @Transactional
    @Override
    public OrderTbl create(String userId, String commodityCode, int orderCount) {
        //1、计算订单价格
        int orderMoney = calculate(commodityCode, orderCount);

        //2、扣减账户余额
        accountFeignClient.debit(userId, orderMoney);
        //3、保存订单
        OrderTbl orderTbl = new OrderTbl();
        orderTbl.setUserId(userId);
        orderTbl.setCommodityCode(commodityCode);
        orderTbl.setCount(orderCount);
        orderTbl.setMoney(orderMoney);

        //3、保存订单
        orderTblMapper.insert(orderTbl);

        return orderTbl;
    }

    // 计算价格
    private int calculate(String commodityCode, int orderCount) {
        return 9*orderCount;
    }
}

持久层

java 复制代码
package com.nie.order.mapper;

import com.nie.order.bean.OrderTbl;

/**
* @author lfy
* @description 针对表【order_tbl】的数据库操作Mapper
* @createDate 2025-01-08 18:34:18
* @Entity com.atguigu.order.bean.OrderTbl
*/
public interface OrderTblMapper {

    int deleteByPrimaryKey(Long id);

    int insert(OrderTbl record);

    int insertSelective(OrderTbl record);

    OrderTbl selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(OrderTbl record);

    int updateByPrimaryKey(OrderTbl record);

}
java 复制代码
<?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.nie.order.mapper.OrderTblMapper">

    <resultMap id="BaseResultMap" type="com.nie.order.bean.OrderTbl">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="userId" column="user_id" jdbcType="VARCHAR"/>
            <result property="commodityCode" column="commodity_code" jdbcType="VARCHAR"/>
            <result property="count" column="count" jdbcType="INTEGER"/>
            <result property="money" column="money" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,user_id,commodity_code,
        count,money
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from order_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from order_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.nie.order.bean.OrderTbl"
            useGeneratedKeys="true">
        insert into order_tbl
        ( id,user_id,commodity_code
        ,count,money)
        values (#{id,jdbcType=INTEGER},#{userId,jdbcType=VARCHAR},#{commodityCode,jdbcType=VARCHAR}
        ,#{count,jdbcType=INTEGER},#{money,jdbcType=INTEGER})
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.nie.order.bean.OrderTbl" useGeneratedKeys="true">
        insert into order_tbl
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">id,</if>
                <if test="userId != null">user_id,</if>
                <if test="commodityCode != null">commodity_code,</if>
                <if test="count != null">count,</if>
                <if test="money != null">money,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">#{id,jdbcType=INTEGER},</if>
                <if test="userId != null">#{userId,jdbcType=VARCHAR},</if>
                <if test="commodityCode != null">#{commodityCode,jdbcType=VARCHAR},</if>
                <if test="count != null">#{count,jdbcType=INTEGER},</if>
                <if test="money != null">#{money,jdbcType=INTEGER},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.nie.order.bean.OrderTbl">
        update order_tbl
        <set>
                <if test="userId != null">
                    user_id = #{userId,jdbcType=VARCHAR},
                </if>
                <if test="commodityCode != null">
                    commodity_code = #{commodityCode,jdbcType=VARCHAR},
                </if>
                <if test="count != null">
                    count = #{count,jdbcType=INTEGER},
                </if>
                <if test="money != null">
                    money = #{money,jdbcType=INTEGER},
                </if>
        </set>
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.nie.order.bean.OrderTbl">
        update order_tbl
        set 
            user_id =  #{userId,jdbcType=VARCHAR},
            commodity_code =  #{commodityCode,jdbcType=VARCHAR},
            count =  #{count,jdbcType=INTEGER},
            money =  #{money,jdbcType=INTEGER}
        where   id = #{id,jdbcType=INTEGER} 
    </update>
</mapper>

配置application.yml

java 复制代码
spring:
  application:
    name: seata-order
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false

server:
  port: 12000
mybatis:
  mapper-locations: classpath:mapper/*.xml

配置seata地址

xml 复制代码
 service {
   #transaction service group mapping
   vgroupMapping.default_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
 }

seata-storage

启动类

java 复制代码
package com.nie.storage;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@EnableTransactionManagement
@MapperScan("com.nie.storage.mapper")
@EnableDiscoveryClient
@SpringBootApplication
public class SeataStorageMainApplication {

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

实体类

java 复制代码
package com.nie.storage.bean;

import java.io.Serializable;
import lombok.Data;

/**
 * @TableName storage_tbl
 */
@Data
public class StorageTbl implements Serializable {
    private Integer id;

    private String commodityCode;

    private Integer count;

    private static final long serialVersionUID = 1L;
}

控制层

java 复制代码
package com.nie.storage.controller;


import com.nie.storage.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StorageRestController {

    @Autowired
    StorageService  storageService;

    @GetMapping("/deduct")
    public String deduct(@RequestParam("commodityCode") String commodityCode,
                         @RequestParam("count") Integer count) {

        storageService.deduct(commodityCode, count);
        return "storage deduct success";
    }
}

业务层

java 复制代码
package com.nie.storage.service;


public interface StorageService {


    /**
     * 扣除存储数量
     * @param commodityCode 商品编码
     * @param count 数量
     */
    void deduct(String commodityCode, int count);
}
java 复制代码
package com.nie.storage.service.impl;

import com.nie.storage.mapper.StorageTblMapper;
import com.nie.storage.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StorageServiceImpl implements StorageService {

    @Autowired
    StorageTblMapper storageTblMapper;



    @Transactional
    @Override
    public void deduct(String commodityCode, int count) {
        storageTblMapper.deduct(commodityCode, count);
        if (count == 5) {
            throw new RuntimeException("库存不足");
        }
    }
}

持久层

java 复制代码
package com.nie.storage.mapper;

import com.nie.storage.bean.StorageTbl;

/**
* @author lfy
* @description 针对表【storage_tbl】的数据库操作Mapper
* @Entity com.atguigu.storage.bean.StorageTbl
*/
public interface StorageTblMapper {

    int deleteByPrimaryKey(Long id);

    int insert(StorageTbl record);

    int insertSelective(StorageTbl record);

    StorageTbl selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(StorageTbl record);

    int updateByPrimaryKey(StorageTbl record);

    void deduct(String commodityCode, int count);
}
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.nie.storage.mapper.StorageTblMapper">

    <resultMap id="BaseResultMap" type="com.nie.storage.bean.StorageTbl">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="commodityCode" column="commodity_code" jdbcType="VARCHAR"/>
            <result property="count" column="count" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,commodity_code,count
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from storage_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from storage_tbl
        where  id = #{id,jdbcType=INTEGER} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.nie.storage.bean.StorageTbl" useGeneratedKeys="true">
        insert into storage_tbl
        ( id,commodity_code,count
        )
        values (#{id,jdbcType=INTEGER},#{commodityCode,jdbcType=VARCHAR},#{count,jdbcType=INTEGER}
        )
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.nie.storage.bean.StorageTbl" useGeneratedKeys="true">
        insert into storage_tbl
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">id,</if>
                <if test="commodityCode != null">commodity_code,</if>
                <if test="count != null">count,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">#{id,jdbcType=INTEGER},</if>
                <if test="commodityCode != null">#{commodityCode,jdbcType=VARCHAR},</if>
                <if test="count != null">#{count,jdbcType=INTEGER},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.nie.storage.bean.StorageTbl">
        update storage_tbl
        <set>
                <if test="commodityCode != null">
                    commodity_code = #{commodityCode,jdbcType=VARCHAR},
                </if>
                <if test="count != null">
                    count = #{count,jdbcType=INTEGER},
                </if>
        </set>
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.nie.storage.bean.StorageTbl">
        update storage_tbl
        set 
            commodity_code =  #{commodityCode,jdbcType=VARCHAR},
            count =  #{count,jdbcType=INTEGER}
        where   id = #{id,jdbcType=INTEGER} 
    </update>
    <update id="deduct">
        update storage_tbl
        set count = count - #{count}
        where commodity_code = #{commodityCode}
    </update>
</mapper>

配置application.yml

java 复制代码
spring:
  application:
    name: seata-storage
  datasource:
    url: jdbc:mysql://localhost:3306/storage_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
server:
  port: 13000

mybatis:
  mapper-locations: classpath:mapper/*.xml

配置seata地址

java 复制代码
 service {
   #transaction service group mapping
   vgroupMapping.default_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
 }

二阶提交协议

  • 一阶:本地事物提交
    (业务数据+undo_log)
    会添加一个全局锁(数据级别)
  • 二阶事物:成功或者失败:
    成功:所有微服务删除undo_log
    失败:所有人拿到自己的前镜像(数据没做更改之前的数据),恢复到之前的数据,删除undo_log

当我们发送一个全局事物的时候

我们可以看到他添加了全局锁

我们的数据库发生了改变

我们的undo_log也有记录

但是当某一个事物出错时 那么发生回滚 数据恢复到之前没有修改的时候

undo_log被删除

四种事物模式

  1. AT模式
    AT模式是Seata框架的默认事务模式,它通过记录SQL执行前后的数据快照(undo log),在需要回滚时,根据这些数据快照进行反向补偿,从而实现事务的原子性和一致性。
  2. XA模式
    XA模式基于X/Open XA规范实现,依赖于数据库对XA协议的支持。它通过两阶段提交(2PC)来保证分布式事务的原子性和一致性。
  3. TCC模式
    TCC模式是一种基于业务逻辑的分布式事务解决方案,它将事务分为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel)。
  4. Sage模式
    SAGA模式是一种长事务解决方案,它将一个长事务拆分为多个本地事务,并通过事件驱动的方式进行协调。
相关推荐
橙子199110166 分钟前
谈谈 Kotlin 中的构造方法,有哪些注意事项?
java·前端·kotlin
麓殇⊙29 分钟前
黑马点评--短信登录实现
java·springboot
Tee xm40 分钟前
算法修仙传 第一章 灵根觉醒:数组基础与遍历
java·数据结构·算法·数组·遍历
叫我黎大侠1 小时前
使用 LibreOffice 实现各种文档格式转换(支持任何开发语言调用 和 Linux + Windows 环境)[全网首发,保姆级教程,建议收藏]
java·大数据·linux·开发语言·python·c#·php
友莘居士2 小时前
创建信任所有证书的HttpClient:Java 实现 HTTPS 接口调用,等效于curl -k
java·开发语言·https·httpclient·curl -k
abc小陈先生2 小时前
JUC并发编程1
java·juc
飞飞9872 小时前
spring mvc
java·服务器·前端
贺函不是涵2 小时前
【沉浸式求职学习day47】【JSP详解】
java·开发语言·学习
曼岛_2 小时前
[Java实战] Docker 快速启动 Sentinel 控制台(二十八)
java·docker·sentinel