spring cloud alibaba Seata分布式事物 详解
安装seata
官网: https://seata.apache.org/zh-cn/download/seata-server
下载后直接解压 进到bin目录 输入cmd
启动命令:seata-server.bat
进入localhost:7091 默认账户跟密码都为seata
seate分布式事务案例

- 创建数据库
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;
- 导入依赖
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>
- 创建四个微服务项目
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被删除
四种事物模式
- AT模式
AT模式是Seata框架的默认事务模式,它通过记录SQL执行前后的数据快照(undo log),在需要回滚时,根据这些数据快照进行反向补偿,从而实现事务的原子性和一致性。 - XA模式
XA模式基于X/Open XA规范实现,依赖于数据库对XA协议的支持。它通过两阶段提交(2PC)来保证分布式事务的原子性和一致性。 - TCC模式
TCC模式是一种基于业务逻辑的分布式事务解决方案,它将事务分为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel)。 - Sage模式
SAGA模式是一种长事务解决方案,它将一个长事务拆分为多个本地事务,并通过事件驱动的方式进行协调。