锋哥原创的分布式事务框架Seata视频教程:
实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)_哔哩哔哩_bilibili实战阿里分布式事务框架Seata视频教程(无废话,通俗易懂版)共计10条视频,包括:1 阿里分布式事务框架Seata简介、2 分布式事务简介、3 SpringCloud Alibaba分布式基础案例搭建等,UP主更多精彩视频,请关注UP账号。https://www.bilibili.com/video/BV1Uf4y1579F/我们模拟一个简单下单业务,客户端调用rest对外服务,rest服务再调用订单服务实现创建订单和账户服务实现账户扣钱操作,最终来完整下单业务;
2.1 案例架构设计
所有服务都注册到nacos中,方便feign远程调用;订单服务,账户服务各自有独立数据库;架构设计如下图:
整体项目结构如下图:
seatatest
是父项目,主要是做一些依赖管理,依赖版本管理,管理所有子module项目;
seata-common
子项目,主要是引入其他子项目需要的公共依赖,以及公共实体,工具类,配置类的统一封装;
seata-order
子项目,主要提供订单服务,生成订单;
seata-account
子项目,主要提供账户服务,根据订单扣钱操作;
seata-web
子项目,主要处理客户端下单请求,feign远程调用order,和account服务接口,最终完成下单处理;
2.2 数据库设计
我们新建两个数据库,分别是db_order
(订单数据库),db_account
(账户数据库),
db_order
数据库里面新建表t_order
订单表:
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orderNo` varchar(100) DEFAULT NULL,
`userId` int(11) DEFAULT NULL,
`count` int(11) DEFAULT NULL,
`amount` int(11) DEFAULT NULL,
`remark` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8
db_account
数据库里面新建表t_account
用户账户表:
CREATE TABLE `t_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) DEFAULT NULL,
`balance` int(11) DEFAULT NULL,
`remark` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
插入数据:
insert into `t_account` (`id`, `userId`, `balance`, `remark`) values('1','1','2000','jack的账户');
insert into `t_account` (`id`, `userId`, `balance`, `remark`) values('2','2','1000','marry的账户');
2.3 seatatest父项目搭建
seatatest是父项目,主要是做一些依赖管理,依赖版本管理,管理所有子module项目;
注意,它的packaging
类型是pom
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.java1234</groupId>
<artifactId>seatatest</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>seata-common</module>
<module>seata-order</module>
<module>seata-account</module>
<module>seata-web</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<druid.version>1.1.10</druid.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<springboot.version>2.3.2.RELEASE</springboot.version>
<springcloudalibaba.version>2.2.4.RELEASE</springcloudalibaba.version>
<fastjson.version>1.2.35</fastjson.version>
<commons-lang3.version>3.6</commons-lang3.version>
<seata-common.version>1.0-SNAPSHOT</seata-common.version>
<mybatis.version>2.1.0</mybatis.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloudalibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>com.java1234</groupId>
<artifactId>seata-common</artifactId>
<version>${seata-common.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.4 seata-common子项目搭建
seata-common
子项目,主要是引入其他子项目需要的公共依赖,以及公共实体,工具类,配置类的统一封装;
项目结构:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seatatest</artifactId>
<groupId>com.java1234</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-common</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- spring boot redis 缓存引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
订单实体Order
:
package com.java1234.entity;
/**订单表实体
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-13 10:26
*/
public class Order {
private Integer id; // 编号
private String orderNo; // 订单号
private Integer userId; // 用户编号
private Integer count; // 购买数量
private Integer amount; // 购买金额
private String remark; // 备注
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
账户实体Account
:
package com.java1234.entity;
/**
* 用户账户表
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-13 10:36
*/
public class Account {
private Integer id; // 编号
private Integer userId; // 用户编号
private Integer balance; // 账户余额
private String remark; // 备注
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
2.5 seata-order子项目搭建
seata-order
子项目,主要提供订单服务,生成订单;
项目结构:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seatatest</artifactId>
<groupId>com.java1234</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order</artifactId>
<dependencies>
<dependency>
<groupId>com.java1234</groupId>
<artifactId>seata-common</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
server:
port: 8081
servlet:
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_order?serverTimezone=Asia/Shanghai
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: seata-order
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
启动类OrderApplication
:
package com.java1234;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@MapperScan("com.java1234.mapper")
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
OrderMapper.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.java1234.mapper.OrderMapper">
<select id="createOrder" parameterType="com.java1234.entity.Order" >
insert into t_order values(null,#{orderNo},#{userId},#{count},#{amount},#{remark})
</select>
</mapper>
OrderMapper
接口:
package com.java1234.mapper;
import com.java1234.entity.Order;
/**
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-13 10:43
*/
public interface OrderMapper {
/**
* 创建订单
* @param order
*/
void createOrder(Order order);
}
OrderService
接口:
package com.java1234.service;
import com.java1234.entity.Order;
/**
* 订单service接口
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-14 11:00
*/
public interface OrderService {
/**
* 创建订单
* @param order
*/
void createOrder(Order order);
}
OrderServiceImpl
实现类:
package com.java1234.service.impl;
import com.java1234.entity.Order;
import com.java1234.mapper.OrderMapper;
import com.java1234.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-14 11:02
*/
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public void createOrder(Order order) {
orderMapper.createOrder(order);
}
}
OrderController
:
package com.java1234.controller;
import com.java1234.entity.Order;
import com.java1234.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-14 11:13
*/
@RestController
@RequestMapping("/seata")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 创建订单
* @param order
* @return
*/
@PostMapping("/createOrder")
public boolean createOrder(@RequestBody Order order){
System.out.println("order:"+order);
order.setOrderNo(UUID.randomUUID().toString()); // 生成订单ID
orderService.createOrder(order);
return true;
}
}
2.6 seata-account子项目搭建
seata-account
子项目,主要提供账户服务,根据订单扣钱操作;
项目结构:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seatatest</artifactId>
<groupId>com.java1234</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-account</artifactId>
<dependencies>
<dependency>
<groupId>com.java1234</groupId>
<artifactId>seata-common</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
server:
port: 8082
servlet:
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_account?serverTimezone=Asia/Shanghai
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: seata-account
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
AccountApplication
启动类:
package com.java1234;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@MapperScan("com.java1234.mapper")
@EnableDiscoveryClient
public class AccountApplication {
public static void main(String[] args) {
SpringApplication.run(AccountApplication.class, args);
}
}
AccountMapper.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.java1234.mapper.AccountMapper">
<select id="decrease" parameterType="Map" >
UPDATE t_account SET balance=balance-#{amount} WHERE userId=#{userId}
</select>
</mapper>
AccountMapper
接口:
package com.java1234.mapper;
import java.util.Map;
/**
* 账户Mapper接口
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-13 10:43
*/
public interface AccountMapper {
/**
* 账户扣钱
*/
void decrease(Map map);
}
AccountService
接口:
package com.java1234.service;
import java.util.Map;
/**
* 账户service接口
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-15 12:58
*/
public interface AccountService {
/**
* 账户扣钱
*/
void decrease(Map map);
}
`AccountServiceImpl`接口实现类:
package com.java1234.service.impl;
import com.java1234.mapper.AccountMapper;
import com.java1234.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 账户Service实现类
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-15 13:00
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void decrease(Map map) {
accountMapper.decrease(map);
}
}
AccountController
:
package com.java1234.controller;
import com.java1234.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 账户Controller
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-15 13:06
*/
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
/**
* 给指定用户账户扣钱
* @param amount
* @param userId
* @return
*/
@PostMapping("/decrease")
public boolean decrease(@RequestParam("amount")Integer amount, @RequestParam("userId")Integer userId){
System.out.println("amount:"+amount+",userId:"+userId);
Map<String,Object> map=new HashMap<>();
map.put("amount",amount);
map.put("userId",userId);
accountService.decrease(map);
return true;
}
}
2.7 seata-web子项目搭建
seata-web
子项目,主要处理客户端下单请求,feign远程调用order,和account服务接口,最终完成下单处理;
项目结构:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seatatest</artifactId>
<groupId>com.java1234</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-web</artifactId>
<dependencies>
<dependency>
<groupId>com.java1234</groupId>
<artifactId>seata-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
server:
port: 80
servlet:
context-path: /
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: seata-web
WebApplication
:
package com.java1234;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableFeignClients(basePackages = "com.java1234.feign")
@EnableDiscoveryClient
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
OrderFeignService
:
package com.java1234.feign;
import com.java1234.entity.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 订单接口feign远程调用
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-16 10:55
*/
@FeignClient("seata-order")
public interface OrderFeignService {
/**
* 创建订单
* @param order
* @return
*/
@PostMapping("/seata/createOrder")
public boolean createOrder(@RequestBody Order order);
}
AccountFeignService
:
package com.java1234.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 账号接口feign远程调用
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-16 10:55
*/
@FeignClient("seata-account")
public interface AccountFeignService {
/**
* 账号扣钱
* @param amount
* @param userId
* @return
*/
@PostMapping("/account/decrease")
public boolean decrease(@RequestParam("amount")Integer amount, @RequestParam("userId")Integer userId);
}
WebController
:
package com.java1234.controller;
import com.java1234.entity.Order;
import com.java1234.feign.AccountFeignService;
import com.java1234.feign.OrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* web-rest接口
* @author java1234_小锋
* @site www.java1234.com
* @company 南通小锋网络科技有限公司
* @create 2021-07-15 16:32
*/
@RestController
public class WebController {
@Autowired
private OrderFeignService orderFeignService;
@Autowired
private AccountFeignService accountFeignService;
/**
* 下单 1,创建订单 2,账户扣钱
* @param order
* @return
*/
@PostMapping("/shopping")
public boolean shopping(Order order){
orderFeignService.createOrder(order); // 创建订单
accountFeignService.decrease(order.getAmount(),order.getUserId()); // 账户扣钱
return true;
}
}
2.8 postman测试
首先启动Nacos服务注册中心:
项目启动:
Nacos控制台:
服务注册成功!
测试接口:http://localhost/shopping
接口测试OK:
订单表生成订单:
账户表id=1的jack账户成功扣减180元;