Seata分布式事务简易项目搭建
本次记载纯个人记载,方便自己以后查看笔记,有错误或者疑惑的地方可以指出来,我也想进步
项目结构
我用的windows 直接使用 tree simple >> 1.txt 然后改下就成下面了
simple
├─simple-storage # 库存demo项目
├─simple-swagger # swagger封装项目
├─simple-web-common # web封装demo项目
├─simple-generator # 基于mp的代码生成demo项目(网上找的)
├─simple-storage-api # storage-api
├─simple-business # 入口demo项目
└─simple-base-common # 基础封装demo项目
复制代码
技术栈
以后扩展再说
技术 | 说明 | 官网 |
---|---|---|
SpringBoot 2.3.5.RELEASE | 容器+MVC框架 | spring.io/projects/sp… |
Mybatis-plus 3.4.1 | ORM框架 | mybatis.plus |
Nacos | 动态服务发现、配置管理和服务管理平台 | nacos.io |
Seata | 分布式事务中间件 | seata.io/zh-cn |
基于idea搭建项目
如果自己已经有了项目可以不用看这个
1. 创建simple
点击 File -> New -> Project, 选择maven,直接点击Next;
录入自己的 项目名和目录名以及相关GAV;
稍等片刻,等项目建立完毕之后,修改pom文件;
<!-- 代码库 -->
<repositories>
<repository>
<id>maven-ali</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
复制代码
右击项目,New -> Moudle… 创建子工程,子工程的创建与父工程创建一致;创建完子项目截图:
2. 添加相关配置
2.1 父工程
2.1.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ph.share</groupId>
<artifactId>ph-simple</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>simple-base-common</module>
<module>simple-web-common</module>
<module>simple-swagger</module>
<module>simple-generator</module>
<module>simple-business</module>
<module>simple-storage</module>
<module>simple-storage-api</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<lombok.version>1.18.16</lombok.version>
<spring.boot.version>2.3.5.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR9</spring.cloud.version>
<alibaba.cloud.version>2.2.5.RELEASE</alibaba.cloud.version>
<mybatisplus.boot.starter.version>3.4.1</mybatisplus.boot.starter.version>
<redisson.version>3.10.1</redisson.version>
<springfox.boot.starter.version>3.0.0</springfox.boot.starter.version>
<swagger-bootstrap-ui.version>1.9.2</swagger-bootstrap-ui.version>
<fastjson.version>1.2.60</fastjson.version>
</properties>
<dependencyManagement>
<dependencies>
<!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.3.3.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies/Hoxton.SR8-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies/2.2.1.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<!--接口文档依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox.boot.starter.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<!--utils-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- 代码库 -->
<repositories>
<repository>
<id>maven-ali</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<!--module不用添加打包版本信息-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
复制代码
2.2 simple-base-common
2.2.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-base-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</project>
复制代码
2.2.2 WrapperResponse
新建包com.ph.web.common.response
package com.ph.web.common.response;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WrapperResponse<T> implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 状态码 0 表示成功
*/
private Integer code;
/**
* 数据
*/
private T data;
/**
* 描述
*/
private String msg;
/**
* 获取远程调用数据
* 注意事项:
* 支持多单词下划线专驼峰(序列化和反序列化)
*
*
* @param typeReference 转换类
* @param <T> 转换数据
* @return 返回响应
*/
public static <T> T getData(TypeReference<T> typeReference, T data){
return JSON.parseObject(JSON.toJSONString(data),typeReference);
}
/**
* 成功,不传入数据
* @return 返回响应
*/
public static <T> WrapperResponse<T> buildSuccess() {
return new WrapperResponse<>(0, null, null);
}
/**
* 成功,传入数据
* @param data 正确响应数据
* @return 返回响应
*/
public static <T> WrapperResponse<T> buildSuccess(T data) {
return new WrapperResponse<>(0, data, null);
}
/**
* 失败,传入描述信息
* @param msg 提示文本
* @return 返回错误响应
*/
public static <T> WrapperResponse<T> buildError(String msg) {
return new WrapperResponse<>(-1, null, msg);
}
/**
* 自定义状态码和错误信息
* @param code 提示编码
* @param msg 提示文本
* @return 返回响应
*/
public static <T> WrapperResponse<T> buildCodeAndMsg(int code, String msg) {
return new WrapperResponse<>(code, null, msg);
}
}
复制代码
2.3 simple-swagger
2.3.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-swagger</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--接口文档依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox.boot.starter.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
<scope>compile</scope>
<version>2.2.5.RELEASE</version>
</dependency>
</dependencies>
</project>
复制代码
2.3.2 spring.factories
# 此处在resources下新建一个META-INF文件 然后touch一个 spring.factories文件 内容如下
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ph.swagger.config.SwaggerConfig
复制代码
2.3.3 SwaggerProperties
新建包 com.ph.swagger.config
package com.ph.swagger.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.Map;
@Data
@ConfigurationProperties(prefix = "swagger")
public class SwaggerProperties {
private boolean enabled = false;
/**
* swagger-ui api 跳转域名
*/
private String apiDomain;
/**
* swagger-ui title
*/
private String title;
/**
* swagger-ui desc
*/
private String description;
/**
* swagger-ui version
*/
private String version;
/**
* swagger-ui scan base package
*/
private String basePackage;
/**
* swagger-ui doc default true
*/
private boolean doc = true;
/**
* swagger-ui apiScanner default false
*/
private boolean apiScanner = false;
/**
* swagger-ui ResourceHandlers - location map
*/
private Map<String, String> hlMap = new HashMap<>();
}
复制代码
2.3.4 SwaggerConfig
package com.ph.swagger.config;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Map;
@AllArgsConstructor
@Configuration
@EnableOpenApi
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true")
public class SwaggerConfig implements WebMvcConfigurer {
private final SwaggerProperties swaggerProperties;
@Bean
public Docket createRestApi() {
if(swaggerProperties.isApiScanner()) {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))// 扫描所有有注解的api,用这种方式更灵活
// .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
.paths(PathSelectors.any())
.build();
}
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
// .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
.termsOfServiceUrl(swaggerProperties.getApiDomain())
.version(swaggerProperties.getVersion())
.build();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
Map<String, String> hlMap = swaggerProperties.getHlMap();
hlMap.forEach((h, l) -> registry.addResourceHandler(h).addResourceLocations(l));
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
if (swaggerProperties.isDoc()) {
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
}
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
复制代码
2.4 simple-web-common
2.4.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-web-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--接口文档依赖-->
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-swagger</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-base-common</artifactId>
<version>1.0</version>
</dependency>
<!--boot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Feign远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--redis客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<!--alibaba微服务整合分布式事务,这个方式才行-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mysql 连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
</project>
复制代码
2.5 simple-generator
2.5.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-generator</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 代码自动生成依赖 begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- 代码自动生成依赖 end-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
复制代码
2.5.2 MyBatisPlusGenerator
创建包 com.ph.generator
package com.ph.generator;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class MyBatisPlusGenerator {
private static final String author = "swallow";
// storage
private static final String outputDir = "F:\\learn\\Seate\\simple\\simple-storage\\src\\main\\java";
private static final String[] tables = new String[] {"t_storage"};
private static final String parentPackage = "com.ph.storage";
private static final String driverName = "com.mysql.jdbc.Driver";
private static final String dbUrl = "jdbc:mysql://ph.base.cn:3306/simple_storage?useSSL=false";
private static final String dbUsername = "root";
private static final String dbPassword = "swallow";
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 是否支持AR模式
config.setActiveRecord(true)
// 作者
.setAuthor(author)
// 生成路径,最好使用绝对路径,window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir(outputDir)
// 文件覆盖
.setFileOverride(true)
// 主键策略
.setIdType(IdType.AUTO)
.setDateType(DateType.ONLY_DATE)
// 设置生成的service接口的名字的首字母是否为I,默认Service是以I开头的
.setServiceName("%sService")
//实体类结尾名称
.setEntityName("%sDO")
//生成基本的resultMap
.setBaseResultMap(true)
//不使用AR模式
.setActiveRecord(false)
//生成基本的SQL片段
.setBaseColumnList(true);
//2. 数据源配置
DataSourceConfig dsConfig = new DataSourceConfig();
// 设置数据库类型
dsConfig.setDbType(DbType.MYSQL)
.setDriverName(driverName)
//TODO TODO TODO TODO
.setUrl(dbUrl)
.setUsername(dbUsername)
.setPassword(dbPassword);
//3. 策略配置globalConfiguration中
StrategyConfig stConfig = new StrategyConfig();
//全局大写命名
stConfig.setCapitalMode(true)
// 去除前缀
.setTablePrefix("t_")
// 数据库表映射到实体的命名策略
.setNaming(NamingStrategy.underline_to_camel)
//使用lombok
.setEntityLombokModel(true)
//使用restcontroller注解
.setRestControllerStyle(true)
// 生成的表, 支持多表一起生成,以数组形式填写
//TODO TODO TODO TODO
.setInclude(tables);
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent(parentPackage)
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("model")
.setXml("mapper");
//5. 整合配置
AutoGenerator ag = new AutoGenerator();
ag.setGlobalConfig(config)
.setDataSource(dsConfig)
.setStrategy(stConfig)
.setPackageInfo(pkConfig);
//6. 执行操作
ag.execute();
System.out.println("======= Done 相关代码生成完毕 ========");
}
}
复制代码
2.6 simple-storage-api
2.6.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-storage-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-base-common</artifactId>
<version>1.0</version>
</dependency>
<!--Feign远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
复制代码
2.6.2 CommodityDTO
创建包 com.ph.storage.dto
package com.ph.storage.dto;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@ToString
@Accessors(chain = true)
public class CommodityDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String commodityCode;
private String name;
private Integer count;
}
复制代码
2.6.3 StorageFeignService
创建包 com.ph.storage.service
package com.ph.storage.service;
import com.ph.storage.dto.CommodityDTO;
import com.ph.web.common.response.WrapperResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "ph-simple-storage", path = "/")
public interface StorageFeignService {
@RequestMapping(value = "/api/storage/dec_storage", method = RequestMethod.POST)
WrapperResponse<Object> decreaseStorage(@RequestBody CommodityDTO commodityDTO);
}
复制代码
2.7 simple-storage
2.7.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-storage</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-storage-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-web-common</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
复制代码
2.7.2 application.yml
# simple系列都是90开头
server:
port: 9003
# swagger配置
swagger:
api-domain: https://storage.simple.com
doc: false
enabled: true
description: ${swagger.title}
base-package: com.ph.storage
title: 仓库api
version: 1.0
spring:
application:
name: ph-simple-storage
# 此处文件上传限制
servlet:
multipart:
max-file-size: 20MB
max-request-size: 20MB
#数据库配置 默认数据库连接池
datasource:
#配置数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ph.base.cn:3306/simple_storage?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: swallow
cloud:
#注册中心地址
nacos:
discovery:
server-addr: ph.base.cn:8848
main:
allow-bean-definition-overriding: true
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 20000
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
#设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
logging:
level:
com.ph: DEBUG
root: INFO
io:
seata:
INFO
seata:
application-id: ph-simple-storage
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
# default: ph.base.cn:8091
default: localhost:8091
# 关闭自动代理 继承了 mybatis-plus 且使用的是 seata-spring-boot-starter 使用下面方法关闭 问题地址见 http://seata.io/zh-cn/docs/overview/faq.html Q: 25. 使用mybatis-plus 动态数据源组件后undolog无法删除 ?
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
server-addr: ph.base.cn:8848
config:
type: nacos
nacos:
server-addr: ph.base.cn:8848
复制代码
2.7.3 StorageApplication
创建包 com.ph.storage
package com.ph.storage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(scanBasePackages = {"com.ph"})
@EnableFeignClients(basePackages = {"com.ph"})
public class StorageApplication {
public static void main(String[] args) {
SpringApplication.run(StorageApplication.class, args);
}
}
复制代码
2.7 4 SeataAutoConfig
创建包 com.ph.storage.config
package com.ph.storage.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class SeataAutoConfig {
private final DataSourceProperties dataSourceProperties;
private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);
private DataSourceProxy dataSourceProxy;
public SeataAutoConfig(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
@Bean(name = "dataSource") // 声明其为Bean实例
@Primary // 在同样的DataSource中,首先使用被标注的DataSource
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl());
druidDataSource.setUrl(dataSourceProperties.getUrl());
druidDataSource.setUsername(dataSourceProperties.getUsername());
druidDataSource.setPassword(dataSourceProperties.getPassword());
druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
druidDataSource.setInitialSize(0);
druidDataSource.setMaxActive(180);
druidDataSource.setMaxWait(60000);
druidDataSource.setMinIdle(0);
druidDataSource.setValidationQuery("Select 1 from DUAL");
druidDataSource.setTestOnBorrow(false);
druidDataSource.setTestOnReturn(false);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
druidDataSource.setMinEvictableIdleTimeMillis(25200000);
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(1800);
druidDataSource.setLogAbandoned(true);
logger.info("装载dataSource........");
dataSourceProxy = new DataSourceProxy(druidDataSource);
return dataSourceProxy;
}
/**
* init datasource proxy
*
* @Param: druidDataSource datasource bean instance
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public DataSourceProxy dataSourceProxy() {
logger.info("代理dataSource........");
return dataSourceProxy;
}
}
复制代码
2.7.5 MybatisPlusConfig
在config包下加入
package com.ph.storage.config;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(value = {"com.ph.storage.mapper"})
public class MybatisPlusConfig {
/**
* mybatis-plus分页插件<br>
* 文档:http://mp.baomidou.com<br>
*/
@Bean
public PaginationInnerInterceptor paginationInterceptor() {
return new PaginationInnerInterceptor();
}
}
复制代码
2.7.6 配置好generagor之后 运行生成相关代码
最后生成目录截图,此处的StorageMapper.xml 是从mapper包下复制粘贴过来的并删除原来的xml
2.7.7 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="com.ph.storage.mapper.StorageMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.ph.storage.model.StorageDO">
<id column="id" property="id" />
<result column="commodity_code" property="commodityCode" />
<result column="name" property="name" />
<result column="count" property="count" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, commodity_code, name, count
</sql>
<update id="decreaseStorage">
update t_storage set count = count-${count} where commodity_code = #{commodityCode} and count >= ${count}
</update>
<select id="loadById" resultType="com.ph.storage.model.StorageDO">
select
id id, commodity_code commodityCode, name name, count count
from t_storage where id = #{id} for update
</select>
</mapper>
复制代码
2.7.8 StorageMapper
package com.ph.storage.mapper;
import com.ph.storage.model.StorageDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* <p>
* Mapper 接口
* </p>
*
* @author swallow
* @since 2021-07-08
*/
public interface StorageMapper extends BaseMapper<StorageDO> {
/**
* 扣减商品库存
* @Param: commodityCode 商品code count扣减数量
* @Return:
*/
int decreaseStorage(@Param("commodityCode") String commodityCode, @Param("count") Integer count);
/**
* 根据id查询
* @param id id
* @Return:
*/
StorageDO loadById(Integer id);
}
复制代码
2.7.9 StorageService
package com.ph.storage.service;
import com.ph.storage.dto.CommodityDTO;
import com.ph.storage.model.StorageDO;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ph.web.common.response.WrapperResponse;
/**
* <p>
* 服务类
* </p>
*
* @author swallow
* @since 2021-07-08
*/
public interface StorageService extends IService<StorageDO> {
WrapperResponse<Object> decreaseStorage(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObject(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObjectAndGlobalLock(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObjectAndGlobalTransactional(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObjectForUpdate(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObjectAndGlobalLockForUpdate(CommodityDTO commodityDTO);
WrapperResponse<StorageDO> loadObjectAndGlobalTransactionalForUpdate(CommodityDTO commodityDTO);
}
复制代码
2.7.10 StorageServiceImpl
package com.ph.storage.service.impl;
import com.ph.storage.dto.CommodityDTO;
import com.ph.storage.model.StorageDO;
import com.ph.storage.mapper.StorageMapper;
import com.ph.storage.service.StorageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ph.web.common.response.WrapperResponse;
import io.seata.spring.annotation.GlobalLock;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* 服务实现类
* </p>
*
* @author swallow
* @since 2021-07-08
*/
@Service
public class StorageServiceImpl extends ServiceImpl<StorageMapper, StorageDO> implements StorageService {
@Override
// @GlobalTransactional
@Transactional
public WrapperResponse<Object> decreaseStorage(CommodityDTO commodityDTO) {
int storage = baseMapper.decreaseStorage(commodityDTO.getCommodityCode(), commodityDTO.getCount());
if (storage > 0) {
return WrapperResponse.buildSuccess();
}
return WrapperResponse.buildError("扣减失败");
}
public WrapperResponse<StorageDO> loadObjectForUpdate(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.loadById(commodityDTO.getId()));
}
@GlobalLock
@Transactional
public WrapperResponse<StorageDO> loadObjectAndGlobalLockForUpdate(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.loadById(commodityDTO.getId()));
}
@GlobalTransactional
public WrapperResponse<StorageDO> loadObjectAndGlobalTransactionalForUpdate(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.loadById(commodityDTO.getId()));
}
public WrapperResponse<StorageDO> loadObject(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.selectById(commodityDTO.getId()));
}
@GlobalLock
@Transactional(rollbackFor = {Throwable.class})
public WrapperResponse<StorageDO> loadObjectAndGlobalLock(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.selectById(commodityDTO.getId()));
}
@GlobalTransactional
public WrapperResponse<StorageDO> loadObjectAndGlobalTransactional(CommodityDTO commodityDTO) {
return WrapperResponse.buildSuccess(baseMapper.selectById(commodityDTO.getId()));
}
}
复制代码
2.7.11 StorageController
package com.ph.storage.controller;
import com.ph.storage.dto.CommodityDTO;
import com.ph.storage.model.StorageDO;
import com.ph.storage.service.StorageService;
import com.ph.web.common.response.WrapperResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 前端控制器
* </p>
*
* @author swallow
* @since 2021-07-08
*/
@RestController
@RequestMapping("/api/storage")
@Slf4j
@RequiredArgsConstructor
public class StorageController {
private final StorageService storageService;
@PostMapping(value = "/dec_storage")
public WrapperResponse<Object> decreaseStorage(@RequestBody CommodityDTO commodityDTO) {
log.info("请求扣减库存:{}", commodityDTO.toString());
return storageService.decreaseStorage(commodityDTO);
}
@GetMapping(value = "/load_object")
public WrapperResponse<StorageDO> loadObject(CommodityDTO commodityDTO) {
return storageService.loadObject(commodityDTO);
}
@GetMapping(value = "/load_object_globalLock")
public WrapperResponse<StorageDO> loadObjectAndGlobalLock(CommodityDTO commodityDTO) {
return storageService.loadObjectAndGlobalLock(commodityDTO);
}
@GetMapping(value = "/load_object_transactional")
public WrapperResponse<StorageDO> loadObjectAndGlobalTransactional(CommodityDTO commodityDTO) {
return storageService.loadObjectAndGlobalTransactional(commodityDTO);
}
@GetMapping(value = "/load_object_for_update")
public WrapperResponse<StorageDO> loadObjectForUpdate(CommodityDTO commodityDTO) {
return storageService.loadObjectForUpdate(commodityDTO);
}
@GetMapping(value = "/load_object_globalLock_for_update")
public WrapperResponse<StorageDO> loadObjectAndGlobalLockForUpdate(CommodityDTO commodityDTO) {
return storageService.loadObjectAndGlobalLockForUpdate(commodityDTO);
}
@GetMapping(value = "/load_object_transactional_for_update")
public WrapperResponse<StorageDO> loadObjectAndGlobalTransactionalForUpdate(CommodityDTO commodityDTO) {
return storageService.loadObjectAndGlobalTransactionalForUpdate(commodityDTO);
}
}
复制代码
2.8 simple-business
2.8.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ph-simple</artifactId>
<groupId>com.ph.share</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simple-business</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-storage-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.ph.share</groupId>
<artifactId>simple-web-common</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
复制代码
2.8.2 application.yml
# simple系列都是90开头
server:
port: 9000
# swagger配置
swagger:
api-domain: https://business.simple.com
doc: false
enabled: true
description: ${swagger.title}
base-package: com.ph.business
title: 入口api
version: 1.0
spring:
application:
name: ph-simple-business
# 此处文件上传限制
servlet:
multipart:
max-file-size: 20MB
max-request-size: 20MB
cloud:
#注册中心地址
nacos:
discovery:
server-addr: ph.base.cn:8848
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 20000
#设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
logging:
level:
root: INFO
seata:
# 首先用seata.applicationId 没有则用spring.cloud.alibaba.seata.applicationId 如果还没有就用 spring.application.name # 见SeataAutoConfiguration.globalTransactionScanner
application-id: ph-simple-business
# 首先用seata.txServiceGroup 如果没有则用 spring.cloud.alibaba.seata.txServiceGroup 如果还没有就用 applicationId + DEFAULT_SPRING_CLOUD_SERVICE_GROUP_POSTFIX(-seata-service-group) 见SeataAutoConfiguration.globalTransactionScanner
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
# default: ph.base.cn:8091
default: localhost:8091
# 关闭自动代理 继承了 mybatis-plus 且使用的是 seata-spring-boot-starter 使用下面方法关闭 问题地址见 http://seata.io/zh-cn/docs/overview/faq.html Q: 25. 使用mybatis-plus 动态数据源组件后undolog无法删除 ?
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
server-addr: ph.base.cn:8848
config:
type: nacos
nacos:
server-addr: ph.base.cn:8848
复制代码
2.8.3 BusinessApplication
创建包 com.ph.business
package com.ph.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;
@SpringBootApplication(scanBasePackages = {"com.ph"})
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.ph"})
public class BusinessApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessApplication.class, args);
}
}
复制代码
2.8.4 BusinessDTO
创建包com.ph.business.dto
package com.ph.business.dto;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class BusinessDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String commodityCode;
private String name;
private Integer count;
private BigDecimal amount;
}
复制代码
2.8.5 BusinessService
创建 com.ph.business.service
package com.ph.business.service;
import com.ph.business.dto.BusinessDTO;
import com.ph.web.common.response.WrapperResponse;
public interface BusinessService {
/**
* 入库处理类
*/
WrapperResponse<Object> handleBusiness(BusinessDTO businessDTO) throws Exception;
}
复制代码
2.8.6 BusinessServiceImpl
创建 com.ph.business.service.impl
package com.ph.business.service.impl;
import com.ph.business.dto.BusinessDTO;
import com.ph.business.service.BusinessService;
import com.ph.storage.dto.CommodityDTO;
import com.ph.storage.service.StorageFeignService;
import com.ph.web.common.response.WrapperResponse;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class BusinessServiceImpl implements BusinessService {
@Autowired
private StorageFeignService storageService;
@Override
@GlobalTransactional(timeoutMills = 300000)
public WrapperResponse<Object> handleBusiness(BusinessDTO businessDTO) throws Exception {
log.info("开启全局事务,XID = " + RootContext.getXID());
//1、扣减库存
CommodityDTO commodityDTO = new CommodityDTO()
.setCommodityCode(businessDTO.getCommodityCode())
.setCount(businessDTO.getCount());
WrapperResponse<Object> storageResponse = storageService.decreaseStorage(commodityDTO);
System.out.println(1 / 0);
if (storageResponse.getCode() != 0) {
throw new Exception("创建订单失败");
}
return WrapperResponse.buildSuccess();
}
}
复制代码
2.8.7 BusinessController
创建包 com.ph.business.controller
package com.ph.business.controller;
import com.ph.business.dto.BusinessDTO;
import com.ph.business.service.BusinessService;
import com.ph.web.common.response.WrapperResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
@RestController
@RequestMapping("/api/business/")
@Slf4j
@RequiredArgsConstructor
public class BusinessController {
private final BusinessService businessService;
@PostMapping(value = "/buy")
public WrapperResponse<Object> handleBusiness(@RequestBody BusinessDTO businessDTO) throws Exception{
log.info("请求参数:{}",businessDTO.toString());
return businessService.handleBusiness(businessDTO);
}
}
复制代码
3. mysql安装
使用docker安装 简单高效
3.1 单机启动
docker run --name s-mysql -e MYSQL_ROOT_PASSWORD=swallow -d -p 3306:3306 -v /root/mysql/data:/var/lib/mysql mysql:5.7
复制代码
3.2 集群搭建
3.2.1 主从搭建
略
3.2.2 mycat 搭建mysql集群
略
3.2.3 新建数据库
-- 新建3个数据库
-- simple_storage
CREATE TABLE `t_storage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `simple_storage`.`t_storage`(`id`, `commodity_code`, `name`, `count`) VALUES (1, 'C201901140001', '水杯', 1000);
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- simple_nacos
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
`src_user` text,
`src_ip` varchar(20) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
);
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
-- simple_seata
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`lock_key` varchar(128) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `lock_table` (
`row_key` varchar(128) NOT NULL,
`xid` varchar(96) DEFAULT NULL,
`transaction_id` mediumtext,
`branch_id` mediumtext,
`resource_id` varchar(256) DEFAULT NULL,
`table_name` varchar(32) DEFAULT NULL,
`pk` varchar(36) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码
4. 运行nacos
使用docker安装 简单高效
4.1 单机启动
#### 对着自己的需求更改
docker run --name=s-nacos -p 8848:8848 -e SPRING_DATASOURCE_PLATFORM=mysql -e MYSQL_SERVICE_HOST=192.168.56.103 -e MYSQL_SERVICE_PORT=3306 -e MYSQL_SERVICE_USER=root -e MYSQL_SERVICE_PASSWORD=swallow -e MYSQL_SERVICE_DB_NAME=learn_nacos -e MODE=standalone -d nacos/nacos-server
复制代码
5. 运行seata
5.1 下载seata包
此处使用的版本是1.3.0
直接去官网下载:seata.io/zh-cn/blog/…
上传到自己的服务器并解压
# 上传到指定目录 目录自己自定义
unzip seata-server-1.3.0.zip
cd seata
# 解析
├── bin
│ ├── seata-server.bat
│ └── seata-server.sh
├── conf
│ ├── file.conf # 主要文件1
│ ├── file.conf.example
│ ├── logback.xml
│ ├── META-INF
│ │ └── services
│ │ ├── io.seata.core.rpc.RegisterCheckAuthHandler
│ │ ├── io.seata.core.store.db.DataSourceProvider
│ │ ├── io.seata.server.coordinator.AbstractCore
│ │ ├── io.seata.server.lock.LockManager
│ │ └── io.seata.server.session.SessionManager
│ ├── README.md
│ ├── README-zh.md
│ └── registry.conf # 主要文件2 注册地址 如果用了所有都用了nacos的话 就不需要file.conf了 此处用的都是nacos
复制代码
5.2 修改registry.conf文件
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.56.190"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "192.168.56.190"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
复制代码
5.3 下载源码找到script/config-center/config.txt文件
记得把# 开头的注释全都去掉哦
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# service 配置 此处需要跟client配置一致
service.vgroupMapping.my_test_tx_group=default
# service 配置 此处填写的是当前seata运行的节点 多个节点用逗号隔开
service.default.grouplist=192.168.56.190:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
# mode修改成db
store.mode=db
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
# 此处修改成db的配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.56.190:3306/simple_seata?useUnicode=true
store.db.user=root
store.db.password=swallow
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.database=0
store.redis.password=null
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
复制代码
5.4 运行python脚本
# python原文
import http.client
import sys
if len(sys.argv) != 2:
print ('python nacos-config.py nacosAddr')
exit()
headers = {
'content-type': "application/x-www-form-urlencoded"
}
hasError = False
# 注意目录
for line in open('./config.txt'):
pair = line.split('=')
if len(pair) < 2:
continue
print (line),
url_prefix = sys.argv[1]
conn = http.client.HTTPConnection(url_prefix)
if len(sys.argv) == 3:
namespace=sys.argv[2]
url_postfix = '/nacos/v1/cs/configs?dataId={0}&group=SEATA_GROUP&content={1}&tenant={2}'.format(str(pair[0]),str(line[line.index('=')+1:]).strip(),namespace)
else:
url_postfix = '/nacos/v1/cs/configs?dataId={}&group=SEATA_GROUP&content={}'.format(str(pair[0]),str(line[line.index('=')+1:])).strip()
conn.request("POST", url_postfix, headers=headers)
res = conn.getresponse()
data = res.read()
if data.decode("utf-8") != "true":
hasError = True
if hasError:
print ("init nacos config fail.")
else:
print ("init nacos config finished, please start seata-server.")
复制代码
5.5 执行
python nacos-config.py 192.168.56.190
# 看到 最后的 init nacos config finished, please start seata-server. 说明正确
复制代码
5.6 在nacos查看是否ok
5.7 启动seata
## 跳转到seata的bin目录 记得装一下jdk哈
cd ~/seata/bin/
sh seata.sh
# 如果 内存不够 直接vim seata.sh 修改启动的参数就行 直接搜 -X 就能看到了 如果启动报错啥的 报错信息都比较明显 什么节点问题呀(单机基本不存在)什么路径问题呀(非nginx配置 基本不存在)其他问题基本没见过 可能我比较帅!!!! 所以八二哥不好意思看我
复制代码
6. 测试
启动storage和business服务 记得修改application.yml 的相关配置,启动报错就遇到没找到对应的服务啥的,然后就是没有注册上,这些都是因为nacos和seata配置出问题导致的(集群环境可能还有端口问题,本人此次将所有的防火墙都关闭了,所以没有遇到太恶心的问题)
先打断点
本人此次使用的是postman测试,其实可以用swagger测试的
点击Send之后进入debug环境,此时看相关数据表
simple_seata的3张表
simple_storage的2张表
释放断点,发现simple_seata的3张事务表全部清空了,simple_storage的undo_log数据表也清空了(如果开启了自动代理,那么可能就会留有一些数据此处解决方案官方已经提供了 我的配置中也有相关链接 问题地址见 seata.io/zh-cn/docs/… Q: 25. 使用mybatis-plus 动态数据源组件后undolog无法删除 ?)
7 思考
- 在处理分布式事务的时候,一个修改接口可能会被很多服务调用(日常工作中其实很少的呀,因为接口其实也是会根据业务或者用户来区分不通数据的,被调用多次的原因大部分是因为rpc服务重复调用导致的(可能很多小伙伴遇见不了,可以模拟呀,开多线程debug模式,然后两个服务之间调用,被调用者打断点,你就能看到这种场景了)),遇到这种场景,本文搭建的环境,就会出现脏数据,目前只能手动处理了(其实可以用定时任务去执行,但是目前处理逻辑比较复杂,性价比极低,而且还需要根据相关业务处理,这点是真的烦,本人没啥好想法,求指点);
- 事务隔离的问题,关于上一个问题,其实也是事务隔离的问题,如果有相关的锁,普通项目都能接受,所以seata提供了 GloabLock+语句for update来处理,这个对于直接写sql的那群人比较友好,而且查询的时候加锁,如果请求链路过长,其他正常请求都会报错,seata也提供了两个参数来处理这个问题:seata.io/zh-cn/docs/… client.rm.lock.retryInterval client.rm.lock.retryTimes 前者 校验或占用全局锁重试间隔 ,后者校验或占用全局锁重试次数,可以直接看官方文档,但是目前测试时木有效果的,查看源码 发现LockProperties 压根没地方用 就初始化加载了下 然后加载的比对地方也没有,也不清楚在哪使用的,暂时不去碰了,有大佬知道可以分享一哈