Spring Cloud微服务案例分布式事务SeataAT&TCC模式一(无事务控制)
1创建空的java父工程
配置项目的maven
如果项目名称没有显示出来可以选择关闭项目再打开
2创建db-init项目
选中seata-at右键新建module选择springboot项目
db-init的pom文件
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>db-init</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>db-init</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
更改application.properties为yml文件,添加以下配置
spring:
datasource:
url: jdbc:mysql://localhost:3307/?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
在resources文件夹下创建sql文件
并添加以下sql文件脚本
account.sql
drop database if exists `seata_account`;
CREATE DATABASE `seata_account` charset utf8;
use `seata_account`;
CREATE TABLE `account` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint(11) UNIQUE DEFAULT NULL COMMENT '用户id',
`total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
`used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
`frozen` decimal(10,0) DEFAULT '0' COMMENT 'TCC事务锁定的金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seata_account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
order.sql
drop database if exists `seata_order`;
CREATE DATABASE `seata_order` charset utf8;
use `seata_order`;
CREATE TABLE `order` (
`id` bigint(11) NOT NULL,
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`count` int(11) DEFAULT NULL COMMENT '数量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' AFTER `money` ;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
CREATE TABLE IF NOT EXISTS segment
(
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键',
VERSION BIGINT DEFAULT 0 NOT NULL COMMENT '版本号',
business_type VARCHAR(63) DEFAULT '' NOT NULL COMMENT '业务类型,唯一',
max_id BIGINT DEFAULT 0 NOT NULL COMMENT '当前最大id',
step INT DEFAULT 0 NULL COMMENT '步长',
increment INT DEFAULT 1 NOT NULL COMMENT '每次id增量',
remainder INT DEFAULT 0 NOT NULL COMMENT '余数',
created_at BIGINT UNSIGNED NOT NULL COMMENT '创建时间',
updated_at BIGINT UNSIGNED NOT NULL COMMENT '更新时间',
CONSTRAINT uniq_business_type UNIQUE (business_type)
) CHARSET = utf8mb4
ENGINE INNODB COMMENT '号段表';
INSERT INTO segment
(VERSION, business_type, max_id, step, increment, remainder, created_at, updated_at)
VALUES (1, 'order_business', 1000, 1000, 1, 0, NOW(), NOW());
seata-server.sql
drop database if exists `seata`;
CREATE DATABASE `seata` CHARSET utf8;
use `seata`;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
storage.sql
drop database if exists `seata_storage`;
CREATE DATABASE `seata_storage` charset utf8;
use `seata_storage`;
CREATE TABLE `storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`total` int(11) DEFAULT NULL COMMENT '总库存',
`used` int(11) DEFAULT NULL COMMENT '已用库存',
`residue` int(11) DEFAULT NULL COMMENT '剩余库存',
`frozen` int(11) DEFAULT '0' COMMENT 'TCC事务锁定的库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seata_storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
在启动了类中添加以下内容
package cn.tedu.dbinit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;
@SpringBootApplication
public class DbInitApplication {
public static void main(String[] args) {
SpringApplication.run(DbInitApplication.class, args);
}
@Autowired
private DataSource dataSource;
/*
spring 的执行流程:
包扫描创建所有实例 --- 完成所有的依赖注入 --- @PostConstruct
*/
@PostConstruct
public void init() throws SQLException {
exec("sql/account.sql");
exec("sql/order.sql");
exec("sql/seata-server.sql");
exec("sql/storage.sql");
}
private void exec(String sql) throws SQLException {
ClassPathResource cpr = new ClassPathResource(
sql, DbInitApplication.class.getClassLoader());
// 处理中文编码
EncodedResource resource = new EncodedResource(cpr, "UTF-8");
ScriptUtils.executeSqlScript(
dataSource.getConnection(),
resource);
}
}
执行main方法就会在数据库中创建数据库表
3新建注册中心eureka-server项目
选中seata-at项目右键module,选择springboot项目,
eureka-server的pom文件
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR11</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
</dependencies>
<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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
把application.properties文件改为application.yml文件
添加以下配置内容
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
server:
#关闭保护模式
enable-self-preservation: false
client:
#对单台服务器,不注册也不拉取
register-with-eureka: false
fetch-registry: false
在主启动类添加@EnableEurekaServer注解
@EnableEurekaServer
启动main方法,访问
http://localhost:8761/
可以看到Eureka启动了
4新建order-parent项目
选择seata-at右键新建module,选择springboot项目
order-parent作为父工程
在order-parent中的pom文件添加以下依赖
<properties>
<mybatis-plus.version>3.3.2</mybatis-plus.version>
<druid-spring-boot-starter.version>1.1.23</druid-spring-boot-starter.version>
<seata.version>1.3.0</seata.version>
<spring-cloud-alibaba-seata.version>2.0.0.RELEASE</spring-cloud-alibaba-seata.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter.version}</version>
</dependency>
<!--<dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-alibaba-seata</artifactId>-->
<!-- <version>${spring-cloud-alibaba-seata.version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <artifactId>seata-all</artifactId>-->
<!-- <groupId>io.seata</groupId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>io.seata</groupId>-->
<!-- <artifactId>seata-all</artifactId>-->
<!-- <version>${seata.version}</version>-->
<!--</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
order-parent作为父工程可以删掉src文件
5新建account项目模块
选中order-parent项目,右键module,选择springboot项目,
调整此项目依赖的的父工程为order-parent,先把原来依赖的parent删掉,选择alt+insert选择parent,选择order-parent
删掉多余重复的依赖
更改application.propertis配置文件为yml文件,配置内容如下
spring:
application:
name: account
datasource:
url: jdbc:mysql://localhost:3307/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 8081 account
# 8082 storage
# 8083 order
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
type-aliases-package: cn.tedu.account.entity
logging:
level:
cn.tedu.account.mapper: debug
添加bootstrap.yml配置网卡(可加可不加)
# 选择网卡
spring:
cloud:
inetutils:
preferred-networks:
- 192\.168\.245\..+
#- 10\.1\.6\..+
新建cn.tedu.account.entity包,创建Account类,
package cn.tedu.account.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private Long id;
private Long userId;
private BigDecimal total; //总金额
private BigDecimal used; //已使用
private BigDecimal residue; //可用
private BigDecimal frozen; //冻结的金额
}
新建cn.tedu.account.service包,创建AccoutService接口
package cn.tedu.account.service;
import java.math.BigDecimal;
public interface AccoutService {
void decrease(Long userId, BigDecimal money);
}
创建AccountServiceImpl类,实现AccountService
package cn.tedu.account.service;
import cn.tedu.account.mapper.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class AccoutServiceImpl implements AccoutService{
@Autowired
private AccountMapper accountMapper;
@Override
public void decrease(Long userId, BigDecimal money) {
accountMapper.decrease(userId,money);
}
}
新建cn.tedu.account.mapper包,创建AccountMapper,继承BaseMapper
package cn.tedu.account.mapper;
import cn.tedu.account.entity.Account;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.math.BigDecimal;
public interface AccountMapper extends BaseMapper<Account> {
void decrease(Long userId, BigDecimal money);
}
在resources中创建文件夹mapper,在文件夹中添加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="cn.tedu.account.mapper.AccountMapper" >
<resultMap id="BaseResultMap" type="cn.tedu.account.entity.Account" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="user_id" property="userId" jdbcType="BIGINT" />
<result column="total" property="total" jdbcType="DECIMAL" />
<result column="used" property="used" jdbcType="DECIMAL" />
<result column="residue" property="residue" jdbcType="DECIMAL"/>
<result column="frozen" property="frozen" jdbcType="DECIMAL"/>
</resultMap>
<update id="decrease">
UPDATE account
SET residue = residue - #{money},used = used + #{money}
where user_id = #{userId};
</update>
</mapper>
新建cn.tedu.account.controller包,添加AccountController类
package cn.tedu.account.controller;
import cn.tedu.account.service.AccoutService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class AccountController {
@Autowired
private AccoutService accoutService;
@GetMapping("/decrease") //decrease?userId=1&money=100
public String decrease(Long userId, BigDecimal money){
accoutService.decrease(userId,money);
return "ok";
}
}
在主启动类上加上包扫描注解
@MapperScan("cn.tedu.account.mapper")
运行main方法
运行结果:
可以在注册中心看到服务已经启动
访问
http://localhost:8081/decrease?userId=1&money=100
运行结果:
6新建storage库存项目
选中order-parent项目,新建module,选择springboot项目,
调整pom文件,将原来的parent删掉,alt+inset选择parent,选择order-parent,将多余的依赖删掉
<?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">
<parent>
<artifactId>order-parent</artifactId>
<groupId>cn.tedu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>storage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>storage</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
</project>
更改application.properties文件为yml文件,添加配置内容
spring:
application:
name: storage
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/seata_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8082
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
mybatis-plus:
type-aliases-package: cn.tedu.storage.entity
mapper-locations:
- classpath:/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.storage.mapper: DEBUG
添加bootstrap.yml(可加可不加)
# 选择网卡
spring:
cloud:
inetutils:
preferred-networks:
- 192\.168\.245\..+
#- 10\.1\.6\..+
编写业务代码:
新建cn.tedu.storage.entity包
添加Storage实体类
package cn.tedu.storage.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage {
private Long id;
private Long productId;
private Integer total;
private Integer used;
private Integer residue;
private Integer frozen;
}
新建cn.tedu.storage.mapper包,在包中创建StorageMapper接口,继承BaseMapper类,
StorageMapper内容如下:
package cn.tedu.storage.mapper;
import cn.tedu.storage.entity.Storage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface StorageMapper extends BaseMapper<Storage> {
void decrease(Long productId,Integer count);
}
在resources文件夹中新建mapper文件夹,并创建StorageMapper.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="cn.tedu.storage.mapper.StorageMapper" >
<resultMap id="BaseResultMap" type="cn.tedu.storage.entity.Storage" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="product_id" property="productId" jdbcType="BIGINT" />
<result column="total" property="total" jdbcType="INTEGER" />
<result column="used" property="used" jdbcType="INTEGER" />
<result column="residue" property="residue" jdbcType="INTEGER" />
</resultMap>
<update id="decrease">
UPDATE storage
SET used = used + #{count},residue = residue - #{count}
WHERE product_id = #{productId}
</update>
</mapper>
添加cn.tedu.storage.service包,新建StorageService接口
package cn.tedu.storage.service;
public interface StorageService {
void decrease(Long productId,Integer count);
}
新建StorageServiceImpl实现类
package cn.tedu.storage.service;
import cn.tedu.storage.mapper.StorageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StorageServiceImpl implements StorageService{
@Autowired
private StorageMapper storageMapper;
@Override
public void decrease(Long productId, Integer count) {
storageMapper.decrease(productId,count);
}
}
新建cn.tedu.storage.controller包,创建StorageController类
package cn.tedu.storage.controller;
import cn.tedu.storage.service.StorageService;
import com.netflix.discovery.converters.Auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
@GetMapping("/decrease")
public String decrease(Long productId,Integer count){
storageService.decrease(productId,count);
return "减少库存成功";
}
}
创建文件的结构
在主启动类加上@MapperScan(“cn.tedu.storage.mapper”)注解
@MapperScan("cn.tedu.storage.mapper")
启动main,可以看到已经在eureka上注册了
浏览器访问http://localhost:8082/decrease?userId=1&money=100,可以看到执行成功
7新建order订单模块
选择order-parent项目,右键新建module,选择springboot项目
调整pom依赖,删掉原来的父工程,alt+insert选择parent,选择order-parent,删掉重复的依赖
更改application.properties文件yml文件,添加以下内容
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
#事务组的组名
cloud:
alibaba:
seata:
tx-service-group: order_tx_group
server:
port: 8083
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
mybatis-plus:
type-aliases-package: cn.tedu.order.entity
mapper-locations:
- classpath:/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.order.mapper: DEBUG
添加bootstrap.yml配置网卡
# 选择网卡
spring:
cloud:
inetutils:
preferred-networks:
- 172\.88\.13\..+
#- 10\.1\.6\..+
新建cn.tedu.order.entity包,创建Order类
package cn.tedu.order.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status;
}
新建cn.tedu.order.mapper包,创建OrderMapper接口,继承BaseMapper
package cn.tedu.order.mapper;
import cn.tedu.order.entity.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface OrderMapper extends BaseMapper<Order> {
void create(Order order);
}
在resources文件夹中创建mapper文件夹,并创建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="cn.tedu.order.mapper.OrderMapper" >
<resultMap id="BaseResultMap" type="cn.tedu.order.entity.Order" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="user_id" property="userId" jdbcType="BIGINT" />
<result column="product_id" property="productId" jdbcType="BIGINT" />
<result column="count" property="count" jdbcType="INTEGER" />
<result column="money" property="money" jdbcType="DECIMAL" />
<result column="status" property="status" jdbcType="INTEGER" />
</resultMap>
<insert id="create">
INSERT INTO `order` (`id`,`user_id`,`product_id`,`count`,`money`,`status`)
VALUES(#{id}, #{userId}, #{productId}, #{count}, #{money},1);
</insert>
</mapper>
新建cn.tedu.order.servie包,新建OrderService接口
package cn.tedu.order.servie;
import cn.tedu.order.entity.Order;
public interface OrderService {
void create(Order order);
}
新建OrderServiceImpl实现类
package cn.tedu.order.servie;
import cn.tedu.order.entity.Order;
import cn.tedu.order.feign.AccountClient;
import cn.tedu.order.feign.EasyIdClient;
import cn.tedu.order.feign.StorageClient;
import cn.tedu.order.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private EasyIdClient easyIdClient;
@Autowired
private StorageClient storageClient;
@Autowired
private AccountClient accountClient;
@Override
public void create(Order order) {
//TODO: 远程调用发号器 生成订单id 生成订单id
String s = easyIdClient.nextId("order_business");
Long orderId = Long.valueOf(s);
order.setId(orderId);
orderMapper.create(order);
//TODO: 调用库存减少商品库存
storageClient.decrease(order.getProductId(),order.getCount());
//ToDO: 远程调用账户扣减账户金额
accountClient.decrease(order.getUserId(),order.getMoney());
}
}
新建cn.tedu.order.controller包,创建OrderController类
package cn.tedu.order.controller;
import cn.tedu.order.entity.Order;
import cn.tedu.order.servie.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;
// /create?userId=1&productId=1&count=10&money=100
@GetMapping("/create")
public String create(Order order){
orderService.create(order);
return "创建订单成功";
}
}
在主函数上加上@MapperScan(“cn.tedu.order.mapper”)注解
@MapperScan("cn.tedu.order.mapper")
运行main函数,可以看到在eureka上进行了注册
访问
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
可以看到执行成功
8添加全局唯一发号器
分布式系统中,产生唯一流水号的服务系统俗称发号器。
有很多发号器开源项目,这里使用 EasyIdGenerator,具体项目信息请访问:https://github.com/lookingatstarts/easyIdGenerator
下载项目访问 https://github.com/lookingatstarts/easyIdGenerator ,下载发号器项目。
解压到 seata-at 工程目录下
解压,和前面的项目放到同一个工程目录。
把目录改名为 easy-id-generator
导入 Module
在Maven工具窗口中点击添加按钮,选择发号器项目的 pom.xml 文件导入该项目:
注意: 如果右侧没有Maven工具标签,请按两下shift键,然后查找 “add maven projects” 就可以找到这个工具。
pom.xml
发号器向 eureka 进行注册,以便其它服务发现它。
在pom.xml 中添加 Spring Cloud Eureka Client 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml
- 配置使用数据库来生成自增id
- 向eureka进行注册
spring:
application:
name: easy-id
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
复制db1.properties更名为seata_order.properties配置内容为
jdbcUrl=jdbc:mysql://localhost:3307/seata_order?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
driverClassName=com.mysql.cj.jdbc.Driver
dataSource.user=root
dataSource.password=root
dataSource.cachePrepStmts=true
dataSource.prepStmtCacheSize=250
dataSource.prepStmtCacheSqlLimit=2048
将application中的db-list配置的内容改为db-list: [“seata_order”]
同时打开segment关闭snowflake
application.yml内容为
server:
port: 9090
easy-id-generator:
snowflake:
enable: false
zk:
connection-string: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
load-worker-id-from-file-when-zk-down: true # 当zk不可访问时,从本地文件中读取之前备份的workerId
segment:
enable: true
db-list: ["seata_order"]
fetch-segment-retry-times: 3 # 从数据库获取号段失败重试次数
spring:
application:
name: easy-id-generator
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
数据表
在 db-init 项目的 order.sql
中已经创建了数据表,并插入了一个名为 order_business
的自增id条目。
项目的 schema.sql 中为示例数据表。
删掉easy-id-generator模块中的lombok的版本控制
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.easy.id</groupId>
<artifactId>easy-id-generator</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<junit.version>4.12</junit.version>
<mysql.connector.version>8.0.16</mysql.connector.version>
<com.alibaba.fastjson.version>1.2.62</com.alibaba.fastjson.version>
<curator.version>2.6.0</curator.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${com.alibaba.fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
启动项目,可以看到已经在eureka注册了
访问测试
根据 SegmentEasyIdController
类的设置,访问下面地址获取自增id:
http://localhost:9090/segment/ids/next_id?businessType=order_business
9order模块订单添加Feign,调用库存和账户服务
- 调用发号器获得全局唯一id
- 调用库存服务减少商品库存
- 调用账户服务扣减用户金额
application.yml
ribbon 默认超时时间是1秒,为了方便分布式事务测试,把超时时长改为 10 秒:
ribbon:
ReadTimeout: 10000
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8083
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
mybatis-plus:
type-aliases-package: cn.tedu.order.entity
mapper-locations:
- classpath:/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.order.mapper: DEBUG
ribbon:
ReadTimeout: 10000
主程序添加注解启用Feign
添加 @EnableFeignClients
注解:
@EnableFeignClients
package cn.tedu.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@MapperScan("cn.tedu.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
在order项目下,
添加Feign声明式客户端接口,发号器的客户端接口:
新建cn.tedu.order.feign包,新建AccountClient接口
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
@FeignClient(name="account")
public interface AccountClient {
@GetMapping("/decrease")
String decrease(@RequestParam("userId") Long userId,
@RequestParam("money") BigDecimal money);
}
新建EasyIdClient接口
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="easy-id")
public interface EasyIdClient {
@GetMapping("/segment/ids/next_id")
String nextId(@RequestParam("businessType")
String businessType);
}
新建StorageClient接口
package cn.tedu.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="storage")
public interface StorageClient {
@GetMapping("/decrease")
String decrease(@RequestParam("productId") Long productId,
@RequestParam("count") Integer count);
}
在业务代码中通过Feign客户端调用远程服务
order模块cn.tedu.order.servie包中的OrderServiceImpl业务代码
package cn.tedu.order.servie;
import cn.tedu.order.entity.Order;
import cn.tedu.order.feign.AccountClient;
import cn.tedu.order.feign.EasyIdClient;
import cn.tedu.order.feign.StorageClient;
import cn.tedu.order.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
EasyIdClient easyIdClient;
@Autowired
private StorageClient storageClient;
@Autowired
private AccountClient accountClient;
@Override
public void create(Order order) {
//TODO: 远程调用发号器 生成订单id 生成订单id
String s = easyIdClient.nextId("order_business");
Long orderId = Long.valueOf(s);
order.setId(orderId);
orderMapper.create(order);
//TODO: 调用库存减少商品库存
storageClient.decrease(order.getProductId(),order.getCount());
//ToDO: 远程调用账户扣减账户金额
accountClient.decrease(order.getUserId(),order.getMoney());
}
}
启动项目,访问测试
访问订单项目进行测试:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
查看 order、storage和account的控制台查看日志
查看三个数据库中的数据变化
库存比之前减少
订单比之前多一条
l业务代码
package cn.tedu.order.servie;
import cn.tedu.order.entity.Order;
import cn.tedu.order.feign.AccountClient;
import cn.tedu.order.feign.EasyIdClient;
import cn.tedu.order.feign.StorageClient;
import cn.tedu.order.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
EasyIdClient easyIdClient;
@Autowired
private StorageClient storageClient;
@Autowired
private AccountClient accountClient;
@Override
public void create(Order order) {
//TODO: 远程调用发号器 生成订单id 生成订单id
String s = easyIdClient.nextId("order_business");
Long orderId = Long.valueOf(s);
order.setId(orderId);
orderMapper.create(order);
//TODO: 调用库存减少商品库存
storageClient.decrease(order.getProductId(),order.getCount());
//ToDO: 远程调用账户扣减账户金额
accountClient.decrease(order.getUserId(),order.getMoney());
}
}
启动项目,访问测试
访问订单项目进行测试:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
查看 order、storage和account的控制台查看日志
查看三个数据库中的数据变化
[外链图片转存中…(img-xSkHapHY-1625230580296)]
[外链图片转存中…(img-Wj2KsO3W-1625230580297)]
[外链图片转存中…(img-bkomGmP2-1625230580297)]
[外链图片转存中…(img-brZyFH3T-1625230580298)]
库存比之前减少
[外链图片转存中…(img-CeZMgvP8-1625230580298)]
订单比之前多一条
[外链图片转存中…(img-0t469jSH-1625230580299)]
[外链图片转存中…(img-RccwYZCn-1625230580300)]