├── .gitignore
├── mysql-data-generator
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── application.yml
│ │ │ └── bootstrap.yml
│ │ └── java
│ │ │ └── com
│ │ │ └── wuda
│ │ │ └── tester
│ │ │ └── mysql
│ │ │ ├── cli
│ │ │ ├── package-info.java
│ │ │ ├── CliArgsRegulator.java
│ │ │ ├── CliArgs.java
│ │ │ └── CliOptionUtils.java
│ │ │ ├── config
│ │ │ ├── package-info.java
│ │ │ └── DatabaseConfiguration.java
│ │ │ ├── statistic
│ │ │ ├── package-info.java
│ │ │ └── DataGenerateStat.java
│ │ │ ├── generate
│ │ │ ├── package-info.java
│ │ │ ├── AlreadyStoppedException.java
│ │ │ ├── DataSet.java
│ │ │ ├── DataGenerateStopPolicy.java
│ │ │ ├── DefaultDataGeneratorStopPolicy.java
│ │ │ ├── FoundationBasedDataGenerator.java
│ │ │ ├── DataGenerator.java
│ │ │ └── FoundationBasedDataSet.java
│ │ │ ├── TableName.java
│ │ │ ├── SpringApplicationContextHolder.java
│ │ │ └── Bootstrap.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── wuda
│ │ └── tester
│ │ └── mysql
│ │ └── cli
│ │ └── CliOptionUtilsTest.java
└── pom.xml
├── mysql-tester-commons
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── wuda
│ └── tester
│ └── mysql
│ └── commons
│ ├── keygen
│ ├── KenGenTimeBackwardsException.java
│ ├── KeyGenExceedMaxValueException.java
│ └── KeyGeneratorSnowflake.java
│ └── utils
│ ├── StringUtilsExt.java
│ └── RandomUtilsExt.java
├── pom.xml
├── README.md
└── mysql_tester.sql
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | target/
4 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/resources/bootstrap.yml:
--------------------------------------------------------------------------------
1 | logging:
2 | level: debug
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * 命令行工具.
3 | *
4 | * @author wuda
5 | */
6 | package com.wuda.tester.mysql.cli;
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/config/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * 管理配置信息.
3 | *
4 | * @author wuda
5 | */
6 | package com.wuda.tester.mysql.config;
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/statistic/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * 统计信息.比如每个表插入了多少数据,失败次数等等.
3 | *
4 | * @author wuda
5 | */
6 | package com.wuda.tester.mysql.statistic;
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * 主要处理数据生成.{@link com.wuda.tester.mysql.generate.DataFactory}类用于为每个表生成Insert into ...Values语句,
3 | * {@link com.wuda.tester.mysql.generate.DataGenerator}类用于组织生成数据的完整逻辑.
4 | *
5 | * @author wuda
6 | */
7 | package com.wuda.tester.mysql.generate;
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/AlreadyStoppedException.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | /**
4 | * 已经停止.
5 | *
6 | * @author wuda
7 | */
8 | public class AlreadyStoppedException extends Exception {
9 |
10 | /**
11 | * serialVersionUID.
12 | */
13 | private static final long serialVersionUID = 1L;
14 | }
15 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/TableName.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql;
2 |
3 | /**
4 | * 表名.
5 | */
6 | public enum TableName {
7 | USER,
8 | USER_ACCOUNT,
9 | INDIVIDUAL_USER_GENERAL,
10 | USER_EMAIL,
11 | USER_PHONE,
12 | STORE,
13 | STORE_GENERAL,
14 | STORE_USER_RELATIONSHIP,
15 | ITEM,
16 | ITEM_GENERAL,
17 | ITEM_VARIATION,
18 | ITEM_DESCRIPTION,
19 | EMAIL,
20 | PHONE,
21 | PROPERTY_KEY,
22 | PROPERTY_VALUE;
23 | }
24 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataSet.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.tester.mysql.cli.CliArgs;
4 |
5 | /**
6 | * 数据集,先在内存中准备好数据,然后这些数据再被插入数据库中.
7 | * 不是一次性把所有数据都生成了,如果要生成5000万数据,那么内存肯定溢出,
8 | * 可以多次生成{@link DataSet},只要最后总的数据量满足即可.
9 | * 通常由{@link com.wuda.tester.mysql.generate.DataGenerator.Producer}生成数据集,然后这些数据
10 | * 再由{@link com.wuda.tester.mysql.generate.DataGenerator.Consumer}消费,也就是插入数据库中.
11 | *
12 | * @author wuda
13 | */
14 | public interface DataSet {
15 |
16 | /**
17 | * 准备好静态数据,这些数据将被插入到数据库中.
18 | *
19 | * @param cliArgs 包含了数据量参数,这样才知道每次生成多少数据量比较合适
20 | */
21 | void prepare(CliArgs cliArgs);
22 | }
23 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/SpringApplicationContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql;
2 |
3 | import org.springframework.context.ApplicationContext;
4 |
5 | /**
6 | * spring application context.
7 | *
8 | * @author wuda
9 | */
10 | public class SpringApplicationContextHolder {
11 |
12 | private static ApplicationContext applicationContext;
13 |
14 | public static void setApplicationContext(ApplicationContext applicationContext) {
15 | SpringApplicationContextHolder.applicationContext = applicationContext;
16 | }
17 |
18 | public static ApplicationContext getApplicationContext() {
19 | return SpringApplicationContextHolder.applicationContext;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataGenerateStopPolicy.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.tester.mysql.statistic.DataGenerateStat;
4 |
5 | /**
6 | * 停止生成数据的策略.尤其是全量数据生成时,不会一直无限制生成, 需要一个停止策略,比如某一个表数据量达到100W条记录时停止.
7 | *
8 | * @author wuda
9 | */
10 | public interface DataGenerateStopPolicy {
11 |
12 | /**
13 | * 根据统计信息决定是否可以停止了.
14 | *
15 | * @param stat 统计信息
16 | * @return true-如果可以停止了
17 | */
18 | boolean stop(DataGenerateStat stat);
19 |
20 | /**
21 | * 当数据生成任务退出时,即{@link #stop(DataGenerateStat)}=true时,
22 | * 给出具体的理由,为什么会做出停止的原因.
23 | *
24 | * @return 停止的原因
25 | */
26 | String getStopMessage();
27 | }
28 |
--------------------------------------------------------------------------------
/mysql-tester-commons/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | mysql-tester
5 | io.github.wuda0112
6 | 1.0.5-SNAPSHOT
7 |
8 | 4.0.0
9 |
10 | mysql-tester-commons
11 |
12 |
13 |
14 | org.apache.commons
15 | commons-lang3
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/test/java/com/wuda/tester/mysql/cli/CliOptionUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.cli;
2 |
3 | import org.junit.Test;
4 |
5 | public class CliOptionUtilsTest {
6 |
7 | @Test
8 | public void parser() {
9 | // String[] args = new String[]{"-h"};
10 | String[] args = new String[]{"--mysql-url=jdbc:mysql://localhost:3306/?serverTimezone=UTC",
11 | "--mysql-username=test",
12 | "--mysql-password=123456",
13 | "--mysql-max-connection=8",
14 | "--thread=30",
15 | "--user-count=100",
16 | "--max-item-per-user=4"};
17 | CliArgs cliArgs = CliOptionUtils.parser(CliOptionUtils.getOptions(), args);
18 | System.out.println(cliArgs);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KenGenTimeBackwardsException.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.commons.keygen;
2 |
3 | /**
4 | * 时间被向后调整异常.
5 | *
6 | * @author wuda
7 | * @version 1.0
8 | */
9 | public class KenGenTimeBackwardsException extends RuntimeException {
10 |
11 | /**
12 | * Constructs a new exception with the specified detail message. The
13 | * cause is not initialized, and may subsequently be initialized by
14 | * a call to {@link #initCause}.
15 | *
16 | * @param message
17 | * the detail message. The detail message is saved for
18 | * later retrieval by the {@link #getMessage()} method.
19 | */
20 | public KenGenTimeBackwardsException(String message) {
21 | super(message);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KeyGenExceedMaxValueException.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.commons.keygen;
2 |
3 | /**
4 | * 超过最大可生成的key异常.
5 | *
6 | * @author wuda
7 | * @version 1.0
8 | */
9 | public class KeyGenExceedMaxValueException extends RuntimeException {
10 |
11 | /**
12 | * Constructs a new exception with the specified detail message. The
13 | * cause is not initialized, and may subsequently be initialized by
14 | * a call to {@link #initCause}.
15 | *
16 | * @param message
17 | * the detail message. The detail message is saved for
18 | * later retrieval by the {@link #getMessage()} method.
19 | */
20 | public KeyGenExceedMaxValueException(String message) {
21 | super(message);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | io.github.wuda0112
6 | yhan-spring-boot-based-parent
7 | 1.0.2
8 |
9 |
10 | mysql-tester
11 | 1.0.5-SNAPSHOT
12 | pom
13 |
14 | mysql-tester
15 |
16 |
17 |
18 | Apache Software License 2.0
19 | http://www.apache.org/licenses/LICENSE-2.0
20 | repo
21 |
22 |
23 |
24 |
25 | https://github.com/wuda0112/mysql-tester
26 | scm:git:git@github.com:wuda0112/mysql-tester.git
27 | scm:git:git@github.com:wuda0112/mysql-tester.git
28 | HEAD
29 |
30 |
31 |
32 |
33 | wuda
34 | wuda0112@gmail.com
35 |
36 |
37 |
38 |
39 | 1.0.0
40 |
41 |
42 |
43 | mysql-data-generator
44 | mysql-tester-commons
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DefaultDataGeneratorStopPolicy.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.tester.mysql.TableName;
4 | import com.wuda.tester.mysql.cli.CliArgs;
5 | import com.wuda.tester.mysql.statistic.DataGenerateStat;
6 |
7 | /**
8 | * 全量数据生成的停止策略.
9 | *
10 | * @author wuda
11 | */
12 | public class DefaultDataGeneratorStopPolicy implements DataGenerateStopPolicy {
13 |
14 | /**
15 | * 退出时,给出具体的描述信息.
16 | */
17 | private String stopMessage;
18 |
19 | /**
20 | * 命令行参数.
21 | */
22 | private CliArgs cliArgs;
23 |
24 | /**
25 | * 生成实例.
26 | *
27 | * @param cliArgs 命令行参数
28 | */
29 | public DefaultDataGeneratorStopPolicy(CliArgs cliArgs) {
30 | this.cliArgs = cliArgs;
31 | }
32 |
33 | @Override
34 | public boolean stop(DataGenerateStat stat) {
35 | if (stat.getInsertedCount(TableName.USER) >= cliArgs.getUserCount()) {
36 | stopMessage = "数据生成任务成功完成";
37 | return true;
38 | } else if (stat.getTotalTaskCount() > cliArgs.getThread() && stat.getFailureRate() > 0.1F) {
39 | stopMessage = "thread=" + cliArgs.getThread()
40 | + ",totalTaskCount=" + stat.getTotalTaskCount()
41 | + ",failureTaskCount=" + stat.getFailureTaskCount()
42 | + ",失败率太高!";
43 | return true;
44 | }
45 | return false;
46 | }
47 |
48 | /**
49 | * 当数据生成任务退出时,即{@link #stop(DataGenerateStat)}=true时,
50 | * 给出具体的理由,为什么会做出停止的原因.
51 | *
52 | * @return 停止的原因
53 | */
54 | public String getStopMessage() {
55 | return stopMessage;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/Bootstrap.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql;
2 |
3 | import com.wuda.tester.mysql.cli.CliArgs;
4 | import com.wuda.tester.mysql.cli.CliArgsRegulator;
5 | import com.wuda.tester.mysql.cli.CliOptionUtils;
6 | import com.wuda.tester.mysql.config.DatabaseConfiguration;
7 | import com.wuda.tester.mysql.generate.DataGenerator;
8 | import com.wuda.tester.mysql.generate.FoundationBasedDataGenerator;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.boot.SpringApplication;
12 | import org.springframework.boot.autoconfigure.SpringBootApplication;
13 | import org.springframework.context.ConfigurableApplicationContext;
14 |
15 | import javax.sql.DataSource;
16 |
17 | /**
18 | * 程序启动类.以SpringBoot项目方式启动.
19 | *
20 | * @author wuda
21 | */
22 | @SpringBootApplication
23 | public class Bootstrap {
24 |
25 | /**
26 | * logger.
27 | */
28 | private static Logger logger = LoggerFactory.getLogger(Bootstrap.class);
29 |
30 | public static void main(String[] args) {
31 | // 解析命令行参数
32 | CliArgs cliArgs = CliOptionUtils.parser(CliOptionUtils.getOptions(), args);
33 | cliArgs.validate();
34 | CliArgsRegulator.regulate(cliArgs);
35 | // spring boot
36 | ConfigurableApplicationContext context = SpringApplication.run(Bootstrap.class, args);
37 | SpringApplicationContextHolder.setApplicationContext(context);
38 | // 使用命令行传过来的数据库参数
39 | DatabaseConfiguration.applyArgs(context.getBean(DataSource.class), cliArgs);
40 | DataSource dataSource = SpringApplicationContextHolder.getApplicationContext().getBean(DataSource.class);
41 |
42 | // 全量数据生成器
43 | DataGenerator generator = new FoundationBasedDataGenerator(cliArgs, dataSource);
44 | generator.startup();
45 | System.out.println("---------数据生成正在进行中......---------");
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliArgsRegulator.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.cli;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * 用于调节命令行参数,避免传入的命令行参数不合理.
8 | *
9 | * @author wuda
10 | */
11 | public class CliArgsRegulator {
12 |
13 | private static Logger logger = LoggerFactory.getLogger(CliArgsRegulator.class);
14 |
15 | /**
16 | * 调节,校准参数.
17 | *
18 | * @param cliArgs 从命令行传递的参数
19 | */
20 | public static void regulate(CliArgs cliArgs) {
21 | regulateMaxItemPerUser(cliArgs);
22 | regulateThreadCount(cliArgs);
23 | }
24 |
25 | /**
26 | * 调节线程数.
27 | */
28 | public static void regulateThreadCount(CliArgs cliArgs) {
29 | int expectedThreadCount = cliArgs.getThread();
30 | int actualThreadCount = expectedThreadCount;
31 | int expectedUserCount = cliArgs.getUserCount();
32 | if (expectedUserCount <= cliArgs.getBatchSize()) {
33 | actualThreadCount = 2;
34 | } else if (expectedUserCount <= expectedThreadCount * cliArgs.getBatchSize()) {
35 | // 比如:总共只需要生成20000个用户,现在有10条线程,每个线程一次性生成10000条数据的情况
36 | actualThreadCount = expectedThreadCount / cliArgs.getBatchSize() + 1;
37 | }
38 | if (actualThreadCount <= 1) {
39 | actualThreadCount = 2;
40 | }
41 | if (actualThreadCount != expectedThreadCount) {
42 | logger.info("生成" + expectedUserCount + "个用户,不需要" + expectedThreadCount + "条线程,重新调整线程数为" + actualThreadCount);
43 | }
44 | cliArgs.setThread(actualThreadCount);
45 | }
46 |
47 | /**
48 | * 调节线程数.
49 | */
50 | public static void regulateMaxItemPerUser(CliArgs cliArgs) {
51 | int expected = cliArgs.getMaxItemPerUser();
52 | int actual = expected;
53 | if (expected >= cliArgs.getMaxItemPerUserExceed()) {
54 | actual = cliArgs.getMaxItemPerUserExceed();
55 | logger.info("每个用户的item数量为" + expected + ",太大,重新调整为" + actual);
56 | }
57 | cliArgs.setMaxItemPerUser(actual);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/config/DatabaseConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.config;
2 |
3 | import com.wuda.tester.mysql.cli.CliArgs;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.apache.tomcat.jdbc.pool.PoolConfiguration;
6 | import org.apache.tomcat.jdbc.pool.PoolProperties;
7 | import org.jooq.DSLContext;
8 | import org.jooq.SQLDialect;
9 | import org.jooq.impl.DSL;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 |
13 | import javax.sql.DataSource;
14 |
15 | /**
16 | * 数据库相关配置.
17 | *
18 | * @author wuda
19 | */
20 | @Configuration
21 | public class DatabaseConfiguration {
22 |
23 | /**
24 | * 获取{@link DataSource}
25 | *
26 | * @return {@link DataSource}
27 | */
28 | @Bean
29 | public DataSource getDataSource() {
30 | PoolConfiguration poolProperties = new PoolProperties();
31 | poolProperties.setDriverClassName("com.mysql.cj.jdbc.Driver");
32 | return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
33 | }
34 |
35 | /**
36 | * 数据库连接的url,username,password等可以从命令行传入,
37 | * 使用命令行传入的数据库连接参数.
38 | *
39 | * @param dataSource datasource
40 | * @param cliArgs 命令行参数
41 | */
42 | public static void applyArgs(DataSource dataSource, CliArgs cliArgs) {
43 | org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
44 | tomcatDataSource.setUrl(cliArgs.getMysqlUrl());
45 | if (StringUtils.isNoneBlank(cliArgs.getMysqlUsername())) {
46 | tomcatDataSource.setUsername(cliArgs.getMysqlUsername());
47 | }
48 | if (StringUtils.isNoneBlank(cliArgs.getMysqlPassword())) {
49 | tomcatDataSource.setPassword(cliArgs.getMysqlPassword());
50 | }
51 | tomcatDataSource.setMaxActive(cliArgs.getMysqlMaxConnection());
52 | }
53 |
54 | /**
55 | * jooq执行sql语句的context
56 | *
57 | * @return context
58 | */
59 | @Bean
60 | public DSLContext getDSLContext() {
61 | return DSL.using(getDataSource(), SQLDialect.MYSQL);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 下载可执行JAR包,生成数据
2 |
3 | ```
4 | 1. 安装Java JDK, Java版本 >= 1.8
5 | ```
6 |
7 | ```
8 | 2. 下载可执行jar文件
9 | ```
10 | - [jar下载](https://github.com/wuda0112/mysql-tester/releases/)
11 |
12 | ```
13 | 3. 创建数据库表,SQL文件和【相应版本的JAR在同一个地方下载】
14 | ```
15 | - [mysql_tester.sql 脚步文件](https://github.com/wuda0112/mysql-tester/releases/)
16 |
17 | ```
18 | 4. 输入命令,启动。默认连接到本地mysql,即: localhost:3306,最简单的就是
19 |
20 | java -jar mysql-tester-${VERSION}.jar --mysql-username=用户名 --mysql-password=密码
21 | ```
22 | ```
23 | 5. 每隔一分钟会输出每个表生成的数据量,比如以下输出,表示STORE表生成了24500条记录
24 | ```
25 | ```
26 | STORE=24500
27 | USER=24500
28 | ITEM_GENERAL=123145
29 | USER_PHONE=24500
30 | ITEM_DESCRIPTION=123145
31 | ITEM=123145
32 | ```
33 |
34 | # clone项目
35 | ```aidl
36 | 1. 启动类: com.wuda.tester.mysql.Bootstrap
37 | 2. 启动之前必须配置 --mysql-username 和 --mysql-password 两个args,默认连接到本地mysql数据库,比如对于IDEA开发工具,
38 | 输入命令行参数的位置是:Run -> Edit Configuritions -> Configration -> Program arguments ,在输入框中输入
39 | --mysql-username=your username --mysql-password=your password 即可
40 | ```
41 |
42 | # 简介
43 | 生成的数据规模是可配置的,比如指定生成100万用户,5000万商品;并且**数据之间有关联关系**,因此可以测试sql join等语句。
44 | 很多工具要么生成的数据是单表,即数据之间没有关联关系,要么数据量较小,对于很多测试看不到效果,本项目的目的就是既生成有关联关系的数据,又可以自定义数据规模!
45 |
46 | # 数据库表
47 | 不同的版本,所使用的表可能不一样,因为一直在增加更多的表。越高的版本,所使用的表的数量越多,已经发布的版本所使用的表,请查看对应版本的SQL文件
48 |
49 | ## 最新发布版本所使用的表,如下
50 | 数据库表选自于我的另外一个中台项目[**foundation**](https://github.com/wuda0112/foundation)
51 | ### foundation_user
52 | - - user,用户表
53 | - - individual_user_general,个人用户基本信息
54 | - - user_account,用户账号信息,适用各种类型的用户
55 | - - user_email,用户的email
56 | - - user_phone,用户的电话
57 | ### foundation_store
58 | - - store,店铺表
59 | - - store_general,店铺基本信息
60 | - - store_user_relationship,如果把用户ID字段放在store表中,表明店铺属于某个用户,但是如果有多个用户可以管理这个店铺呢?有种做法是一个用户作为另一个用户的子账号;也可以建立用户与店铺的关联关系,这样感觉更符合逻辑。把用户IID放在store表,可以很明确的表明店铺的owner,如果是用关联关系表的话,就需要明确的标明哪个用户是owner,哪些用户只是管理这个店铺的。
61 | ### foundation_item
62 | - - item,物品(商品)表
63 | - - item_general,物品基本信息
64 | - - item_description,物品描述信息
65 | - - item_variation,物品规格
66 | ### foundation_commons
67 | - - phone,电话表
68 | - - email,邮箱表
69 | - - property_key,属性的key
70 | - - property_value,属性的值
71 |
72 | # 数据量配置
73 | 查看 --user-count 和 --max-item-per-user 两个选项的说明
74 |
75 | # 支持的参数(必须放在mysql-tester-${VERSION}.jar后面)
76 |
77 | ```
78 | --max-item-per-user
79 | 每个用户最多有多少商品数;在生成数据时,随机为每个用户生成商品,数量取值范围是
80 | [0,MAX](default=10).比如默认生成10000个用户,每个用户
81 | 最多10个商品,那么大致就可以知道生成的数据规模
82 | --mysql-max-connection mysql最大连接数(default=25),不是越大越好
83 | --mysql-password mysql password
84 | --mysql-url
85 | mysql连接url(default=jdbc:mysql://localho
86 | st:3306/?serverTimezone=UTC)
87 | --mysql-username mysql username
88 | --thread 生成数据的线程数(default=50),不是越大越好
89 | --user-count
90 | 用户表生成多少行记录,同时也是店铺表和仓库表的记录数,因为一个用户只拥有一个店
91 | 铺和一个仓库(default=10000),当生成的记录数达到该值时,数据生成
92 | 任务结束
93 | ```
94 |
95 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliArgs.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.cli;
2 |
3 | import lombok.Data;
4 | import lombok.ToString;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | * 命令行接口输入的参数生成的实体.方便程序后续调用.
11 | *
12 | * @author wuda
13 | */
14 | @Data
15 | @ToString
16 | public class CliArgs {
17 |
18 | /**
19 | * logger.
20 | */
21 | private Logger logger = LoggerFactory.getLogger(CliArgs.class);
22 |
23 | final static String HELP = "help";
24 | final static String HELP_DESC = "print this help message";
25 | final static String HELP_SHORT = "h";
26 |
27 | final static String THREAD = "thread";
28 | final static int THREAD_DEFAULT = 50;
29 | final static String THREAD_DESC = "生成数据的线程数(default=" + THREAD_DEFAULT + ")";
30 | private int thread = THREAD_DEFAULT;
31 |
32 | final static String MYSQL_URL = "mysql-url";
33 | final static String MYSQL_URL_DEFAULT = "jdbc:mysql://localhost:3306/?serverTimezone=UTC";
34 | final static String MYSQL_URL_DESC = "mysql连接url(default=" + MYSQL_URL_DEFAULT + ")";
35 | private String mysqlUrl = MYSQL_URL_DEFAULT;
36 |
37 | final static String MYSQL_USERNAME = "mysql-username";
38 | final static String MYSQL_USERNAME_DESC = "mysql username";
39 | private String mysqlUsername;
40 |
41 | final static String MYSQL_PASSWORD = "mysql-password";
42 | final static String MYSQL_PASSWORD_DESC = "mysql password";
43 | private String mysqlPassword;
44 |
45 | final static String MYSQL_MAX_CONNECTION = "mysql-max-connection";
46 | final static int MYSQL_MAX_CONNECTION_DEFAULT = THREAD_DEFAULT / 2;
47 | final static String MYSQL_MAX_CONNECTION_DESC = "mysql最大连接数(default=" + MYSQL_MAX_CONNECTION_DEFAULT + ")";
48 | private int mysqlMaxConnection = MYSQL_MAX_CONNECTION_DEFAULT;
49 |
50 | final static String USER_COUNT = "user-count";
51 | final static int USER_COUNT_DEFAULT = 10000;
52 | final static String USER_COUNT_DESC = "用户表生成多少行记录,同时也是店铺表和仓库表的记录数,因为一个用户只拥有一个店铺和一个仓库" +
53 | "(default=" + USER_COUNT_DEFAULT + ")" +
54 | ",当生成的记录数达到该值时,数据生成任务结束";
55 | private int userCount = USER_COUNT_DEFAULT;
56 |
57 | final static String MAX_ITEM_PER_USER = "max-item-per-user";
58 | final static int MAX_ITEM_PER_USER_DEFAULT = 10;
59 | final static String MAX_ITEM_PER_USER_DESC = "每个用户最多有多少商品数;在生成数据时,随机为每个用户生成商品,数量取值范围是[0,MAX]" +
60 | "(default=" + MAX_ITEM_PER_USER_DEFAULT + ")." +
61 | "比如默认生成" + USER_COUNT_DEFAULT + "个用户,每个用户最多" + MAX_ITEM_PER_USER_DEFAULT + "个商品," +
62 | "那么大致就可以知道生成的数据规模";
63 | private int maxItemPerUser = MAX_ITEM_PER_USER_DEFAULT;
64 | private int maxItemPerUserExceed = 20;
65 |
66 | final static String BATCH_SIZE = "batch-size";
67 | public final static int BATCH_SIZE_DEFAULT = 100;
68 | final static String BATCH_SIZE_DESC = "每个线程一次性批量生成的用户数.为了提高生成数据的性能,使用\n" +
69 | " \n" +
70 | " insert into table\n" +
71 | " (column)\n" +
72 | " values(a),\n" +
73 | " ` (b),\n" +
74 | " (c);\n" +
75 | " 这样的语法,这个值就表示value的数量." +
76 | "(default=" + BATCH_SIZE_DEFAULT + ")";
77 | private int batchSize = BATCH_SIZE_DEFAULT;
78 |
79 | /**
80 | * 自我校验.
81 | */
82 | public void validate() {
83 | if (StringUtils.isBlank(getMysqlUsername())) {
84 | logger.warn("没有指定数据库用户名,在命令行参数中,使用--" + MYSQL_USERNAME + "设置");
85 | System.exit(0);
86 | }
87 | if (StringUtils.isBlank(getMysqlPassword())) {
88 | logger.warn("没有指定数据库密码,在命令行参数中,使用--" + MYSQL_PASSWORD + "设置");
89 | System.exit(0);
90 | }
91 | }
92 |
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/mysql-data-generator/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | io.github.wuda0112
7 | mysql-tester
8 | 1.0.5-SNAPSHOT
9 |
10 |
11 | mysql-data-generator
12 | mysql-data-generator
13 | http://maven.apache.org
14 |
15 |
16 | 3.13.2
17 | 1.0.2
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-configuration-processor
28 |
29 |
30 | org.springframework
31 | spring-tx
32 |
33 |
34 | org.springframework
35 | spring-jdbc
36 |
37 |
38 | org.apache.tomcat
39 | tomcat-jdbc
40 |
41 |
42 | javax.persistence
43 | javax.persistence-api
44 |
45 |
46 | commons-cli
47 | commons-cli
48 |
49 |
50 |
51 | mysql
52 | mysql-connector-java
53 |
54 |
55 |
56 | io.github.wuda0112
57 | mysql-tester-commons
58 | ${project.version}
59 |
60 |
61 |
62 |
63 | io.github.wuda0112
64 | foundation-lang
65 | ${foundation.version}
66 |
67 |
68 | io.github.wuda0112
69 | foundation-commons-impl
70 | ${foundation.version}
71 |
72 |
73 | io.github.wuda0112
74 | foundation-user-impl
75 | ${foundation.version}
76 |
77 |
78 | io.github.wuda0112
79 | foundation-store-impl
80 | ${foundation.version}
81 |
82 |
83 | io.github.wuda0112
84 | foundation-item-impl
85 | ${foundation.version}
86 |
87 |
88 |
89 |
90 |
91 | mysql-tester-${version}
92 |
93 |
94 | org.springframework.boot
95 | spring-boot-maven-plugin
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/utils/StringUtilsExt.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.commons.utils;
2 |
3 | /**
4 | * string util.
5 | *
6 | * @author wuda
7 | */
8 | public class StringUtilsExt {
9 |
10 | /**
11 | * 空字符串.
12 | */
13 | public static String EMPTY_STRING = "";
14 |
15 | /**
16 | * 大小写枚举.
17 | */
18 | public static enum CaseEnum {
19 | upper, lower;
20 | }
21 |
22 | /**
23 | * 将第一个字符大写.
24 | *
25 | * @param str 字符串
26 | * @return 新的字符串
27 | */
28 | public static String firstCharToUpperCase(String str) {
29 | return changeCaseAt(str, 0, CaseEnum.upper);
30 | }
31 |
32 | /**
33 | * 将第一个字符小写.
34 | *
35 | * @param str 字符串
36 | * @return 新的字符串
37 | */
38 | public static String firstCharToLowerCase(String str) {
39 | return changeCaseAt(str, 0, CaseEnum.lower);
40 | }
41 |
42 | /**
43 | * 改变指定位置字符的大小写.
44 | *
45 | * @param str string
46 | * @param index 位置
47 | * @param lowerOrUpper lower or upper
48 | * @return new string
49 | */
50 | public static String changeCaseAt(String str, int index, CaseEnum lowerOrUpper) {
51 | if (str == null || str.isEmpty()) {
52 | return str;
53 | }
54 | if (index >= str.length()) {
55 | return str;
56 | }
57 | char oldChar = str.charAt(index);
58 | char newChar = 0x00;
59 | switch (lowerOrUpper) {
60 | case upper:
61 | newChar = Character.toUpperCase(oldChar);
62 | break;
63 | case lower:
64 | newChar = Character.toLowerCase(oldChar);
65 | break;
66 | }
67 | if (newChar == oldChar) {
68 | return str;
69 | }
70 | char[] copy = str.toCharArray();
71 | copy[index] = newChar;
72 | return new String(copy);
73 | }
74 |
75 | /**
76 | * 为字符串添加给定的前缀.
77 | *
78 | * @param str 原始字符串
79 | * @param prefix 前缀
80 | * @return 前缀与原始字符串组成的新字符串
81 | */
82 | public static String addPrefix(String str, String prefix) {
83 | if (prefix == null || prefix.isEmpty()) {
84 | return str;
85 | }
86 | if (str == null || str.isEmpty()) {
87 | return prefix;
88 | }
89 | char[] chars = new char[str.length() + prefix.length()];
90 | for (int i = 0; i < prefix.length(); i++) {
91 | chars[i] = prefix.charAt(i);
92 | }
93 | int k = prefix.length();
94 | for (int j = 0; j < str.length(); j++) {
95 | chars[k++] = str.charAt(j);
96 | }
97 | return new String(chars);
98 | }
99 |
100 | /**
101 | * 为字符串添加给定的后缀.
102 | *
103 | * @param str 原始字符串
104 | * @param suffix 后缀
105 | * @return 原始字符串与后缀组成的新字符串
106 | */
107 | public static String addSuffix(String str, String suffix) {
108 | return addPrefix(suffix, str);
109 | }
110 |
111 | /**
112 | * 去除掉最后一个char.
113 | *
114 | * @param charSequence char sequence
115 | * @return 去除最后一个字符后的字符串
116 | */
117 | public static String removeLastChar(CharSequence charSequence) {
118 | if (charSequence == null) {
119 | return null;
120 | } else if (charSequence.length() <= 1) {
121 | return EMPTY_STRING;
122 | } else {
123 | return charSequence.subSequence(0, lastCharIndex(charSequence)).toString();
124 | }
125 | }
126 |
127 | /**
128 | * 最后一个字符的下标.
129 | *
130 | * @param charSequence char sequence
131 | * @return 最后一个字符的下标
132 | */
133 | public static int lastCharIndex(CharSequence charSequence) {
134 | return charSequence.length() - 1;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/utils/RandomUtilsExt.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.commons.utils;
2 |
3 |
4 | import org.apache.commons.lang3.RandomUtils;
5 |
6 | /**
7 | * 对Apache RandomUtils的扩展.补充一些其他的随机方法.
8 | *
9 | * @author wuda
10 | */
11 | public class RandomUtilsExt {
12 |
13 | /**
14 | * 在给定的数组中随机选择一个数字.
15 | *
16 | * @param candidates 候选数字,至少要有一个元素,否则抛出异常
17 | * @return 数组中的任意一个数字
18 | */
19 | public static int nextInt(int candidates[]) {
20 | if (candidates == null || candidates.length <= 0) {
21 | throw new IllegalArgumentException("候选数组中至少要有一个元素!");
22 | }
23 | int index = RandomUtils.nextInt(0, candidates.length);
24 | return candidates[index];
25 | }
26 |
27 | /**
28 | * 随机字符串.
29 | *
30 | * @param length 字符串长度,如果小于1,则返回空字符串(不是null).
31 | * @return 字符串
32 | */
33 | public static String randomString(int length) {
34 | return randomString(length, Character.MIN_CODE_POINT, Character.MAX_CODE_POINT);
35 | }
36 |
37 | public static String enRandomString(int length) {
38 | int[] enCodePoints = new int[]{65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90};
39 | StringBuilder stringBuilder = new StringBuilder(length);
40 | for (int k = 0; k < length; k++) {
41 | char ch = (char) nextInt(enCodePoints);
42 | stringBuilder.append(ch);
43 | }
44 | return stringBuilder.toString();
45 | }
46 |
47 | /**
48 | * 生成随机字符串.字符从给定的code point范围内随机获取.
49 | *
50 | * @param length 长度
51 | * @param codePointStartInclusive unicode code point范围的起始值,包含
52 | * @param codePointEndExclusive unicode code point范围的结束值,不包含
53 | * @return 随机字符串
54 | */
55 | public static String randomString(int length, int codePointStartInclusive, int codePointEndExclusive) {
56 | if (length <= 0) {
57 | return StringUtilsExt.EMPTY_STRING;
58 | }
59 | StringBuilder builder = new StringBuilder(length);
60 | for (int i = 0; i < length; i++) {
61 | builder.append(randomChar(codePointStartInclusive, codePointEndExclusive));
62 | }
63 | return builder.toString();
64 | }
65 |
66 | /**
67 | * 随机生成字符串.至少包含英文字母,汉字,数字中的一种.
68 | *
69 | * @param length 字符串长度
70 | * @param hasLetter 是否包含英文字母
71 | * @param hasHan 是否包含中文汉字
72 | * @param hasNumber 是否包含数字
73 | * @return 随机字符串
74 | */
75 | public static String randomString(int length, boolean hasLetter, boolean hasHan, boolean hasNumber) {
76 | if (!hasLetter && !hasHan && !hasNumber) {
77 | throw new IllegalArgumentException("至少包含一种字符!");
78 | }
79 | if (length <= 0) {
80 | return StringUtilsExt.EMPTY_STRING;
81 | }
82 | return null;
83 | }
84 |
85 | /**
86 | * 在给定的范围内生成随机char.
87 | *
88 | * @param codePointStartInclusive unicode code point 结束值,不包含
89 | * @param codePointEndExclusive unicode code point 起始值,包含
90 | * @return char
91 | */
92 | public static char randomChar(int codePointStartInclusive, int codePointEndExclusive) {
93 | return (char) RandomUtils.nextInt(codePointStartInclusive, codePointEndExclusive);
94 | }
95 |
96 | /**
97 | * 随机给出一个char.
98 | *
99 | * @return char
100 | */
101 | public static char randomChar() {
102 | return randomChar(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT);
103 | }
104 |
105 | /**
106 | * 常用邮箱.
107 | */
108 | static String[] emailSuffix = new String[]{"@163.com", "@126.com", "@gmail.com", "@sina.com", "@sina.cn", "@qq.com"};
109 |
110 | /**
111 | * 随机给出一个邮箱后缀,即@符号以后(包含)的部分.比如: @163.com
112 | *
113 | * @return 随机的邮箱后缀
114 | */
115 | public static String randomEmailSuffix() {
116 | int index = RandomUtils.nextInt(0, emailSuffix.length);
117 | return emailSuffix[index];
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/mysql-tester-commons/src/main/java/com/wuda/tester/mysql/commons/keygen/KeyGeneratorSnowflake.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.commons.keygen;
2 |
3 | /**
4 | * Twitter Snowflake算法实现.用于分布式环境下生成不重复ID.
5 | *
6 | * @author wuda
7 | * @version 1.0
8 | */
9 | public class KeyGeneratorSnowflake {
10 |
11 | private int time_bit_count = 41; // bit数量,用于表示时间
12 | private int node_bit_count = 10; // bit数量,用于表示集群中节点
13 | private int sequence_bit_count = 12; // bit数量,用于表示序列号
14 | private long epoch = 1523289600000L;// 此算法编码完成的时间
15 |
16 | private int time_shift = sequence_bit_count + node_bit_count;
17 | private int node_shift = sequence_bit_count;
18 |
19 | private long max_timestamp = epoch + ((1L << time_bit_count) - 1); // 最大时间戳
20 | private int max_node_no = (1 << node_bit_count) - 1; // 最大节点编号
21 | private int max_sequence = (1 << sequence_bit_count) - 1; // 最大序列号
22 |
23 | private long timestamp;
24 | private long nodeNo;// 代表集群中节点的编号
25 | private long sequence = 0;
26 |
27 | /**
28 | * 为给定的节点构造一个key生成器.
29 | *
30 | * @param nodeNo 代表集群中节点的编号,从0开始
31 | */
32 | public KeyGeneratorSnowflake(int nodeNo) {
33 | checkNodeNo(nodeNo);
34 | this.nodeNo = nodeNo;
35 | try {
36 | this.timestamp = getTimestamp();
37 | } catch (KeyGenExceedMaxValueException | KenGenTimeBackwardsException e) {
38 | throw new IllegalStateException(e);
39 | }
40 | }
41 |
42 | /**
43 | * 检查给定的节点编号是否在指定范围内.
44 | *
45 | * @param nodeNo 节点编号
46 | */
47 | private void checkNodeNo(int nodeNo) {
48 | if (nodeNo < 0 || nodeNo > max_node_no) {
49 | throw new IllegalArgumentException("节点编号允许的范围是:[0," + max_node_no + "],当前节点编号是:" + nodeNo);
50 | }
51 | }
52 |
53 | /**
54 | * 返回下一个key.
55 | *
56 | * @return 下一个key
57 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常
58 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了
59 | */
60 | public synchronized long next() throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException {
61 | sequence++;
62 | if (sequence > max_sequence) {
63 | timestamp = getTimestamp();
64 | sequence = 0;
65 | }
66 | return ((timestamp - epoch) << time_shift) | (nodeNo << node_shift) | sequence;
67 | }
68 |
69 | /**
70 | * 返回指定数量的key.
71 | *
72 | * @param size 一次性获取多个key
73 | * @return keys
74 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常
75 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了
76 | */
77 | public synchronized long[] next(int size) throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException {
78 | if (size <= 0) {
79 | throw new IllegalArgumentException("size必须大于0");
80 | }
81 | if (size == 1) {
82 | return new long[]{next()};
83 | }
84 | long[] keys = new long[size];
85 | for (int i = 0; i < size; i++) {
86 | keys[i] = next();
87 | }
88 | return keys;
89 | }
90 |
91 | /**
92 | * 获取时间戳.
93 | *
94 | * @return 时间戳
95 | * @throws KeyGenExceedMaxValueException 此生成器不能无限生成key,会有一个最大值,当超过这个最大值时,抛出此异常
96 | * @throws KenGenTimeBackwardsException 机器的时间被向后调整了
97 | */
98 | private long getTimestamp() throws KeyGenExceedMaxValueException, KenGenTimeBackwardsException {
99 | long currentTimeMillis = System.currentTimeMillis();
100 | if (currentTimeMillis < epoch) {
101 | throw new KenGenTimeBackwardsException("时间被向后调了?当前机器的时间戳是:" + currentTimeMillis +
102 | ",纪元时间戳是:" + epoch);
103 | }
104 | while (currentTimeMillis == timestamp) {
105 | currentTimeMillis = System.currentTimeMillis();
106 | }
107 | if (currentTimeMillis < timestamp) {
108 | throw new KenGenTimeBackwardsException("时间被向后调了?当前机器的时间戳是:" + currentTimeMillis +
109 | ",最近一次记录的时间戳是:" + timestamp);
110 | }
111 | if (currentTimeMillis > max_timestamp) {
112 | throw new KeyGenExceedMaxValueException("omg!你的系统已经运行了差不多69年,恭喜!然而此ID生成器已经无法再继续提供服务了,bye!");
113 | }
114 | return currentTimeMillis;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/statistic/DataGenerateStat.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.statistic;
2 |
3 | import com.wuda.tester.mysql.TableName;
4 |
5 | import java.util.Map;
6 | import java.util.Set;
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | /**
11 | * 数据生成过程中的统计信息.该类不是线程安全的,所以统计信息不是精确的, 但是也不会太离谱,尽可能会减小误差.
12 | *
13 | * @author wuda
14 | */
15 | public class DataGenerateStat {
16 |
17 | /**
18 | * 数据生成任务执行的总次数.
19 | */
20 | private AtomicInteger totalTaskCount = new AtomicInteger();
21 | /**
22 | * 数据生成任务成功的次数.
23 | */
24 | private AtomicInteger successTaskCount = new AtomicInteger();
25 | /**
26 | * 数据生成任务失败的次数.
27 | */
28 | private AtomicInteger failureTaskCount = new AtomicInteger();
29 |
30 | /**
31 | * 已经保存到数据库的数量.key-表名,value-数量
32 | */
33 | private Map inserted_entity_count = new ConcurrentHashMap<>();
34 |
35 | public DataGenerateStat() {
36 | TableName[] tableNames = TableName.values();
37 | for (TableName tableName : tableNames) {
38 | inserted_entity_count.put(tableName, new AtomicInteger());
39 | }
40 | }
41 |
42 | /**
43 | * 获取统计的指标,以及该指标的数量.
44 | *
45 | * @return metric and count
46 | */
47 | public Set> getTableCount() {
48 | return inserted_entity_count.entrySet();
49 | }
50 |
51 | /**
52 | * 获取给定统计指标已经插入的数量.
53 | *
54 | * @param tableName 统计指标
55 | * @return 数量
56 | */
57 | public int getInsertedCount(TableName tableName) {
58 | AtomicInteger counter = inserted_entity_count.get(tableName);
59 | if (counter != null) {
60 | return counter.get();
61 | }
62 | return 0;
63 | }
64 |
65 | /**
66 | * 给定的统计指标数量加一,并且返回增加后的值.
67 | *
68 | * @param tableName 统计指标
69 | * @param count 增加的数量
70 | * @return 增加后的数量
71 | */
72 | public int insertedIncrementAndGet(TableName tableName, int count) {
73 | return inserted_entity_count.get(tableName).addAndGet(count);
74 | }
75 |
76 | /**
77 | * 增加并且获取{@link #totalTaskCount}.
78 | *
79 | * @return int
80 | */
81 | public int incrementAndGetTotalTaskCount() {
82 | return totalTaskCount.incrementAndGet();
83 | }
84 |
85 | /**
86 | * 获取{@link #totalTaskCount}.
87 | *
88 | * @return int
89 | */
90 | public int getTotalTaskCount() {
91 | return totalTaskCount.get();
92 | }
93 |
94 | /**
95 | * 增加并且获取{@link #successTaskCount}.
96 | *
97 | * @return int
98 | */
99 | public int incrementAndGetSuccessTaskCount() {
100 | return successTaskCount.incrementAndGet();
101 | }
102 |
103 | /**
104 | * 获取{@link #successTaskCount}.
105 | *
106 | * @return int
107 | */
108 | public int getSuccessTaskCount() {
109 | return successTaskCount.get();
110 | }
111 |
112 | /**
113 | * 增加并且获取{@link #failureTaskCount}.
114 | *
115 | * @return int
116 | */
117 | public int incrementAndGetFailureTaskCount() {
118 | return failureTaskCount.incrementAndGet();
119 | }
120 |
121 | /**
122 | * 获取{@link #failureTaskCount}.
123 | *
124 | * @return int
125 | */
126 | public int getFailureTaskCount() {
127 | return failureTaskCount.get();
128 | }
129 |
130 | /**
131 | * 获取失败率.
132 | *
133 | * @return 失败率
134 | */
135 | public float getFailureRate() {
136 | int totalPlus1 = totalTaskCount.get() + 1;
137 | return (float) failureTaskCount.get() / totalPlus1;
138 | }
139 |
140 | public String toString() {
141 | StringBuilder builder = new StringBuilder("生成数据统计信息:");
142 | builder.append("totalTaskCount =").append(totalTaskCount).append(",")
143 | .append("successTaskCount = ").append(successTaskCount).append(",")
144 | .append("failureTaskCount = " + failureTaskCount).append(".\n");
145 | Set> entrySet = inserted_entity_count.entrySet();
146 | for (Map.Entry entry : entrySet) {
147 | builder.append("\t").append(entry.getKey()).append("=").append(entry.getValue()).append("\n");
148 | }
149 | return builder.toString();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/FoundationBasedDataGenerator.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.foundation.commons.EmailManager;
4 | import com.wuda.foundation.commons.PhoneManager;
5 | import com.wuda.foundation.commons.impl.EmailManagerImpl;
6 | import com.wuda.foundation.commons.impl.PhoneManagerImpl;
7 | import com.wuda.foundation.commons.impl.PropertyManagerImpl;
8 | import com.wuda.foundation.commons.property.PropertyManager;
9 | import com.wuda.foundation.item.ItemManager;
10 | import com.wuda.foundation.item.impl.ItemManagerImpl;
11 | import com.wuda.foundation.store.StoreManager;
12 | import com.wuda.foundation.store.impl.StoreManagerImpl;
13 | import com.wuda.foundation.user.UserManager;
14 | import com.wuda.foundation.user.impl.UserManagerImpl;
15 | import com.wuda.tester.mysql.TableName;
16 | import com.wuda.tester.mysql.cli.CliArgs;
17 |
18 | import javax.sql.DataSource;
19 |
20 | /**
21 | * 基于foundation项目生成数据.
22 | *
23 | * @author wuda
24 | */
25 | public class FoundationBasedDataGenerator extends DataGenerator {
26 |
27 | private long opUserId = 1L;
28 |
29 | /**
30 | * 构造实例.
31 | *
32 | * @param cliArgs 命令行参数
33 | * @param dataSource datasource
34 | */
35 | public FoundationBasedDataGenerator(CliArgs cliArgs, DataSource dataSource) {
36 | super(cliArgs, dataSource);
37 | }
38 |
39 | @Override
40 | public FoundationBasedDataSet prepareDataSet(CliArgs cliArgs) {
41 | FoundationBasedDataSet dataSet = new FoundationBasedDataSet();
42 | dataSet.prepare(cliArgs);
43 | return dataSet;
44 | }
45 |
46 | @Override
47 | public void insert(FoundationBasedDataSet dataSet) {
48 | generateUserData(dataSet);
49 | generateStore(dataSet);
50 | generateItem(dataSet);
51 | generateProperty(dataSet);
52 | }
53 |
54 | public void generateUserData(FoundationBasedDataSet dataSet) {
55 | UserManager userManager = getUserManager();
56 | EmailManager emailManager = getEmailManager();
57 | PhoneManager phoneManager = getPhoneManager();
58 |
59 | userManager.directBatchInsertUser(dataSet.getUsers(), opUserId);
60 | userManager.directBatchInsertUserAccount(dataSet.getUserAccounts(), opUserId);
61 | emailManager.directBatchInsertEmail(dataSet.getEmails(), opUserId);
62 | userManager.directBatchBindUserEmail(dataSet.getBindUserEmails(), opUserId);
63 | phoneManager.directBatchInsertPhone(dataSet.getPhones(), opUserId);
64 | userManager.directBatchBindUserPhone(dataSet.getBindUserPhones(), opUserId);
65 |
66 | dataGenerateStat.insertedIncrementAndGet(TableName.USER, dataSet.getUsers().size());
67 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_ACCOUNT, dataSet.getUserAccounts().size());
68 | dataGenerateStat.insertedIncrementAndGet(TableName.EMAIL, dataSet.getEmails().size());
69 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_EMAIL, dataSet.getBindUserEmails().size());
70 | dataGenerateStat.insertedIncrementAndGet(TableName.PHONE, dataSet.getPhones().size());
71 | dataGenerateStat.insertedIncrementAndGet(TableName.USER_PHONE, dataSet.getBindUserPhones().size());
72 | }
73 |
74 | public void generateStore(FoundationBasedDataSet dataSet) {
75 | StoreManager storeManager = getStoreManager();
76 |
77 | storeManager.directBatchInsertStore(dataSet.getStores(), opUserId);
78 | storeManager.directBatchBindStoreUser(dataSet.getBindStoreUsers(), opUserId);
79 | storeManager.directBatchInsertStoreGeneral(dataSet.getStoreGenerals(), opUserId);
80 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE, dataSet.getStores().size());
81 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE_USER_RELATIONSHIP, dataSet.getBindStoreUsers().size());
82 | dataGenerateStat.insertedIncrementAndGet(TableName.STORE_GENERAL, dataSet.getStoreGenerals().size());
83 | }
84 |
85 | public void generateItem(FoundationBasedDataSet dataSet) {
86 | ItemManager itemManager = getItemManager();
87 |
88 | itemManager.directBatchInsertItem(dataSet.getItems(), opUserId);
89 | itemManager.directBatchInsertItemGeneral(dataSet.getItemGenerals(), opUserId);
90 | itemManager.directBatchInsertItemVariation(dataSet.getItemVariations(), opUserId);
91 | itemManager.directBatchInsertItemDescription(dataSet.getItemDescriptions(), opUserId);
92 |
93 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM, dataSet.getItems().size());
94 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_GENERAL, dataSet.getItemGenerals().size());
95 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_VARIATION, dataSet.getItemVariations().size());
96 | dataGenerateStat.insertedIncrementAndGet(TableName.ITEM_DESCRIPTION, dataSet.getItemDescriptions().size());
97 | }
98 |
99 | public void generateProperty(FoundationBasedDataSet dataSet) {
100 | PropertyManager propertyManager = getPropertyManager();
101 |
102 | propertyManager.directBatchInsertPropertyKey(dataSet.getPropertyKeys(), opUserId);
103 | propertyManager.directBatchInsertPropertyValue(dataSet.getPropertyValues(), opUserId);
104 |
105 | dataGenerateStat.insertedIncrementAndGet(TableName.PROPERTY_KEY, dataSet.getPropertyKeys().size());
106 | dataGenerateStat.insertedIncrementAndGet(TableName.PROPERTY_VALUE, dataSet.getPropertyValues().size());
107 | }
108 |
109 | private UserManager getUserManager() {
110 | UserManagerImpl userManager = new UserManagerImpl();
111 | userManager.setDataSource(dataSource);
112 | return userManager;
113 | }
114 |
115 | private EmailManager getEmailManager() {
116 | EmailManagerImpl emailManager = new EmailManagerImpl();
117 | emailManager.setDataSource(dataSource);
118 | return emailManager;
119 | }
120 |
121 | private PhoneManager getPhoneManager() {
122 | PhoneManagerImpl phoneManager = new PhoneManagerImpl();
123 | phoneManager.setDataSource(dataSource);
124 | return phoneManager;
125 | }
126 |
127 | private StoreManager getStoreManager() {
128 | StoreManagerImpl storeManager = new StoreManagerImpl();
129 | storeManager.setDataSource(dataSource);
130 | return storeManager;
131 | }
132 |
133 | private ItemManager getItemManager() {
134 | ItemManagerImpl storeManager = new ItemManagerImpl();
135 | storeManager.setDataSource(dataSource);
136 | return storeManager;
137 | }
138 |
139 | private PropertyManager getPropertyManager() {
140 | PropertyManagerImpl propertyManager = new PropertyManagerImpl();
141 | propertyManager.setDataSource(dataSource);
142 | return propertyManager;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/DataGenerator.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.tester.mysql.cli.CliArgs;
4 | import com.wuda.tester.mysql.statistic.DataGenerateStat;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import javax.sql.DataSource;
9 | import java.util.Date;
10 | import java.util.concurrent.BlockingQueue;
11 | import java.util.concurrent.LinkedBlockingQueue;
12 | import java.util.concurrent.ThreadFactory;
13 | import java.util.concurrent.atomic.AtomicInteger;
14 |
15 | /**
16 | * 数据生成器,随机生成数据并且插入数据库.在生成数据的过程中,使用{@link DataGenerateStat}统计,
17 | * 然后{@link DataGenerateStopPolicy}根据这些统计信息就可以判断是否停止生成数据,
18 | * 确定可以停止后,启动的线程将会停止并退出.
19 | *
20 | * @param 具体的{@link DataSet}的类型
21 | * @author wuda
22 | */
23 | public abstract class DataGenerator {
24 |
25 | /**
26 | * logger.
27 | */
28 | private static Logger logger = LoggerFactory.getLogger(DataGenerator.class);
29 | /**
30 | * thread factory.
31 | */
32 | private static DefaultThreadFactory threadFactory = new DefaultThreadFactory();
33 | /**
34 | * 数据生成过程中的统计信息.
35 | */
36 | protected DataGenerateStat dataGenerateStat = new DataGenerateStat();
37 | /**
38 | * 停止数据生成策略.
39 | */
40 | private DataGenerateStopPolicy stopPolicy;
41 |
42 | private CliArgs cliArgs;
43 | private BlockingQueue queue;
44 | protected DataSource dataSource;
45 |
46 | /**
47 | * 系统启动时间.
48 | */
49 | private Date startupTime;
50 |
51 | /**
52 | * 构造实例.
53 | *
54 | * @param cliArgs 命令行参数
55 | * @param dataSource datasource
56 | */
57 | public DataGenerator(CliArgs cliArgs, DataSource dataSource) {
58 | this.cliArgs = cliArgs;
59 | this.stopPolicy = new DefaultDataGeneratorStopPolicy(cliArgs);
60 | this.queue = new LinkedBlockingQueue<>(cliArgs.getThread());
61 | this.dataSource = dataSource;
62 | }
63 |
64 | /**
65 | * 正在运行的Consumer的数量.
66 | */
67 | private AtomicInteger runningConsumerCount = new AtomicInteger(0);
68 | /**
69 | * 正在运行的Producer的数量.
70 | */
71 | private AtomicInteger runningProducerCount = new AtomicInteger(0);
72 |
73 | /**
74 | * 准备数据.
75 | *
76 | * @param cliArgs 包含了数据量参数,这样才知道每次生成多少数据量比较合适
77 | * @return data set
78 | */
79 | public abstract T prepareDataSet(CliArgs cliArgs);
80 |
81 | /**
82 | * 将准备好的数据插入到数据库中.
83 | *
84 | * @param dataSet 准备好的数据
85 | */
86 | public abstract void insert(T dataSet);
87 |
88 |
89 | /**
90 | * 根据总的线程数,分配Producer占用的线程数.
91 | *
92 | * @param totalThreadSize 总的线程数
93 | * @return Producer的线程数
94 | */
95 | public static int allocateProducerThreadSize(int totalThreadSize) {
96 | float producerThreadSize = totalThreadSize * 0.01F;
97 | return (int) Math.ceil(producerThreadSize);
98 | }
99 |
100 | /**
101 | * 线程sleep.当catch到{@link InterruptedException}异常,使用{@link RuntimeException}的方式将异常抛出.
102 | *
103 | * @param millis millis
104 | */
105 | public void threadSleepSilence(long millis) {
106 | try {
107 | Thread.sleep(millis);
108 | } catch (InterruptedException e) {
109 | throw new RuntimeException("线程sleep时收到了InterruptedException", e);
110 | }
111 | }
112 |
113 | /**
114 | * 启动生产者,消费者,监控线程.
115 | */
116 | public void startup() {
117 | startupTime = new Date();
118 | int producerSize = allocateProducerThreadSize(cliArgs.getThread());
119 | int consumerSize = cliArgs.getThread() - producerSize;
120 | for (int i = 0; i < producerSize; i++) {
121 | threadFactory.newThread(new Producer("Producer-" + i)).start();
122 | }
123 | threadSleepSilence(30 * 1000); // 让producer先产生下数据
124 | for (int i = 0; i < consumerSize; i++) {
125 | threadFactory.newThread(new Consumer("Consumer-" + i)).start();
126 | }
127 | MonitorThread monitorThread = new MonitorThread();
128 | monitorThread.setDaemon(true);
129 | monitorThread.start();
130 |
131 | while (runningConsumerCount.get() > 0 || runningProducerCount.get() > 0) {
132 | threadSleepSilence(60 * 1000);
133 | }
134 | }
135 |
136 | /**
137 | * the Producer.
138 | *
139 | * @author wuda
140 | */
141 | public class Producer implements Runnable {
142 |
143 | /**
144 | * Producer的名称.
145 | */
146 | private String name;
147 |
148 | /**
149 | * 构造指定名称的Producer.
150 | *
151 | * @param name 名称
152 | */
153 | public Producer(String name) {
154 | this.name = name;
155 | }
156 |
157 | @Override
158 | public void run() {
159 | runningProducerCount.incrementAndGet();
160 | while (!stopPolicy.stop(dataGenerateStat)) {
161 | T dataSet = prepareDataSet(cliArgs);
162 | try {
163 | queue.put(dataSet);
164 | } catch (InterruptedException e) {
165 | logger.warn("producer = {} Interrupted", name);
166 | }
167 | }
168 | runningProducerCount.decrementAndGet();
169 | if (runningProducerCount.get() <= 0) {
170 | logger.info(dataGenerateStat.toString());
171 | }
172 | logger.info("Producer ={} 退出,{}", name, stopPolicy.getStopMessage());
173 | }
174 | }
175 |
176 | /**
177 | * the Consumer.
178 | *
179 | * @author wuda
180 | */
181 | public class Consumer implements Runnable {
182 |
183 | /**
184 | * consumer的名称.
185 | */
186 | private String name;
187 |
188 | /**
189 | * 构造指定名称的Consumer.
190 | *
191 | * @param name 名称
192 | */
193 | public Consumer(String name) {
194 | this.name = name;
195 | }
196 |
197 | @Override
198 | public void run() {
199 | runningConsumerCount.incrementAndGet();
200 | T dataSet = null;
201 | while ((dataSet = queue.poll()) != null || runningProducerCount.get() > 0) {
202 | if (dataSet != null) {
203 | try {
204 | dataGenerateStat.incrementAndGetTotalTaskCount();
205 | insert(dataSet);
206 | dataGenerateStat.incrementAndGetSuccessTaskCount();
207 | } catch (Exception e) {
208 | logger.error("Consumer = {},生成数据异常", name, e);
209 | dataGenerateStat.incrementAndGetFailureTaskCount();
210 | }
211 | } else {
212 | logger.info("queue没有获取到元素,可以考虑加大Producer数量,Consumer count ={} ,Producer count = {}"
213 | , runningConsumerCount.get(), runningProducerCount.get());
214 | }
215 | }
216 | runningConsumerCount.decrementAndGet();
217 | logger.info("Consumer ={} 退出 ", name);
218 | }
219 | }
220 |
221 | /**
222 | * 用于监控生成数据的情况.
223 | *
224 | * @author wuda
225 | */
226 | public class MonitorThread extends Thread {
227 |
228 | @Override
229 | public void run() {
230 | while (runningConsumerCount.get() > 0 || runningProducerCount.get() > 0) {
231 | logger.info("启动时间: {}", startupTime);
232 | logger.info("DataSet queue中暂存的DataSet数量是:{},Consumer Count = {},Producer Count = {}"
233 | , queue.size(), runningConsumerCount.get(), runningProducerCount.get());
234 | logger.info(dataGenerateStat.toString());
235 | threadSleepSilence(60 * 1000);
236 | }
237 | logger.info("monitor thread exit");
238 | }
239 |
240 | }
241 |
242 | /**
243 | * The default thread factory
244 | */
245 | static class DefaultThreadFactory implements ThreadFactory {
246 | private final ThreadGroup group;
247 | private final AtomicInteger threadNumber = new AtomicInteger(1);
248 | private final String namePrefix;
249 |
250 | DefaultThreadFactory() {
251 | SecurityManager s = System.getSecurityManager();
252 | group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
253 | namePrefix = "generator-";
254 | }
255 |
256 | public Thread newThread(Runnable r) {
257 | Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
258 | if (t.isDaemon())
259 | t.setDaemon(false);
260 | if (t.getPriority() != Thread.NORM_PRIORITY)
261 | t.setPriority(Thread.NORM_PRIORITY);
262 | return t;
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/cli/CliOptionUtils.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.cli;
2 |
3 | import org.apache.commons.cli.*;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | /**
9 | * 命令行工具类.
10 | *
11 | * @author wuda
12 | */
13 | public class CliOptionUtils {
14 |
15 | private static Logger logger = LoggerFactory.getLogger(CliOptionUtils.class);
16 |
17 | /**
18 | * 支持的选项.
19 | *
20 | * @return 选项
21 | */
22 | public static Options getOptions() {
23 | // 定义一个可选选项集
24 | Options options = new Options();
25 | options.addOption(Option.builder(CliArgs.HELP_SHORT)
26 | .longOpt(CliArgs.HELP)
27 | .argName(CliArgs.HELP)
28 | .desc(CliArgs.HELP_DESC)
29 | .build());
30 | options.addOption(Option.builder()
31 | .longOpt(CliArgs.THREAD)
32 | .argName(CliArgs.THREAD)
33 | .hasArg()
34 | .desc(CliArgs.THREAD_DESC)
35 | .build());
36 | options.addOption(Option.builder()
37 | .longOpt(CliArgs.MYSQL_URL)
38 | .hasArg()
39 | .desc(CliArgs.MYSQL_URL_DESC)
40 | .build());
41 | options.addOption(Option.builder()
42 | .longOpt(CliArgs.MYSQL_USERNAME)
43 | .hasArg()
44 | .desc(CliArgs.MYSQL_USERNAME_DESC)
45 | .build());
46 | options.addOption(Option.builder()
47 | .longOpt(CliArgs.MYSQL_PASSWORD)
48 | .hasArg()
49 | .desc(CliArgs.MYSQL_PASSWORD_DESC)
50 | .build());
51 | options.addOption(Option.builder()
52 | .longOpt(CliArgs.MYSQL_MAX_CONNECTION)
53 | .hasArg()
54 | .desc(CliArgs.MYSQL_MAX_CONNECTION_DESC)
55 | .build());
56 | options.addOption(Option.builder()
57 | .longOpt(CliArgs.USER_COUNT)
58 | .hasArg()
59 | .desc(CliArgs.USER_COUNT_DESC)
60 | .build());
61 | options.addOption(Option.builder()
62 | .longOpt(CliArgs.MAX_ITEM_PER_USER)
63 | .hasArg()
64 | .desc(CliArgs.MAX_ITEM_PER_USER_DESC)
65 | .build());
66 |
67 | return options;
68 | }
69 |
70 | /**
71 | * 解析命令行参数.如果解析失败,则直接退出jvm,即:{@link System#exit(int)}.
72 | *
73 | * @param options 支持的选项
74 | * @param args 命令行参数
75 | * @return {@link CliArgs}对象
76 | */
77 | public static CliArgs parser(Options options, String[] args) {
78 | // 初始化一个命令行解析器,一般用默认的就可以
79 | CommandLineParser parser = new DefaultParser();
80 | CliArgs cliArgs = new CliArgs();
81 | String cmdLineSyntax = "支持的选项";
82 | try {
83 | CommandLine commandLine = parser.parse(options, args);
84 | helpOption(options, commandLine, cmdLineSyntax);
85 | threadOption(commandLine, cliArgs);
86 | mysqlUrlOption(commandLine, cliArgs);
87 | mysqlUsernameOption(commandLine, cliArgs);
88 | mysqlPasswordOption(commandLine, cliArgs);
89 | mysqlMaxConnectionOption(commandLine, cliArgs);
90 | userCountOption(commandLine, cliArgs);
91 | maxItemPerUserOption(commandLine, cliArgs);
92 | } catch (Exception e) {
93 | // 因为本来就是java命令启动的程序,还有很多其他java自身的选项,
94 | // 最坏情况就是不识别的选项不处理而已
95 | HelpFormatter helpFormatter = new HelpFormatter();
96 | if (!e.getClass().equals(org.apache.commons.cli.UnrecognizedOptionException.class)) {
97 | helpFormatter.printHelp(cmdLineSyntax, options);
98 | System.out.println("\n命令行参数解析异常," + e.getMessage());
99 | exit0();
100 | } else {
101 | helpFormatter.printHelp(cmdLineSyntax, options);
102 | logger.warn(e.getMessage(), e);
103 | }
104 | }
105 | return cliArgs;
106 | }
107 |
108 | private static void helpOption(Options options, CommandLine commandLine, String cmdLineSyntax) {
109 | // 如果存在 -h --help 参数
110 | if (commandLine.hasOption(CliArgs.HELP)) {
111 | HelpFormatter helpFormatter = new HelpFormatter();
112 | helpFormatter.printHelp(cmdLineSyntax, options);
113 | exit0();
114 | }
115 | }
116 |
117 | private static void threadOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException {
118 | if (commandLine.hasOption(CliArgs.THREAD)) {
119 | String optionValue = commandLine.getOptionValue(CliArgs.THREAD);
120 | if (StringUtils.isNotBlank(optionValue)) {
121 | String message = "选项" + CliArgs.THREAD + "只支持数字类型,并且必须大于0";
122 | try {
123 | int thread = Integer.parseInt(optionValue);
124 | if (thread <= 0) {
125 | throw new ParseException(message);
126 | }
127 | cliArgs.setThread(thread);
128 | } catch (Exception e) {
129 | throw new ParseException(message);
130 | }
131 | }
132 | }
133 | }
134 |
135 | private static void mysqlUrlOption(CommandLine commandLine, CliArgs cliArgs) {
136 | if (commandLine.hasOption(CliArgs.MYSQL_URL)) {
137 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_URL);
138 | if (StringUtils.isNotBlank(optionValue)) {
139 | cliArgs.setMysqlUrl(optionValue);
140 | }
141 | }
142 | }
143 |
144 | private static void mysqlUsernameOption(CommandLine commandLine, CliArgs cliArgs) {
145 | if (commandLine.hasOption(CliArgs.MYSQL_USERNAME)) {
146 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_USERNAME);
147 | if (StringUtils.isNotBlank(optionValue)) {
148 | cliArgs.setMysqlUsername(optionValue);
149 | }
150 | }
151 | }
152 |
153 | private static void mysqlPasswordOption(CommandLine commandLine, CliArgs cliArgs) {
154 | if (commandLine.hasOption(CliArgs.MYSQL_PASSWORD)) {
155 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_PASSWORD);
156 | if (StringUtils.isNotBlank(optionValue)) {
157 | cliArgs.setMysqlPassword(optionValue);
158 | }
159 | }
160 | }
161 |
162 | private static void mysqlMaxConnectionOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException {
163 | if (commandLine.hasOption(CliArgs.MYSQL_MAX_CONNECTION)) {
164 | String optionValue = commandLine.getOptionValue(CliArgs.MYSQL_MAX_CONNECTION);
165 | if (StringUtils.isNotBlank(optionValue)) {
166 | String message = "选项" + CliArgs.MYSQL_MAX_CONNECTION + "只支持数字类型,并且必须大于0";
167 | try {
168 | int maxConnection = Integer.parseInt(optionValue);
169 | if (maxConnection <= 0) {
170 | throw new ParseException(message);
171 | }
172 | cliArgs.setMysqlMaxConnection(maxConnection);
173 | } catch (Exception e) {
174 | throw new ParseException(message);
175 | }
176 | }
177 | }
178 | }
179 |
180 | private static void userCountOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException {
181 | if (commandLine.hasOption(CliArgs.USER_COUNT)) {
182 | String optionValue = commandLine.getOptionValue(CliArgs.USER_COUNT);
183 | if (StringUtils.isNotBlank(optionValue)) {
184 | String message = "选项" + CliArgs.USER_COUNT + "只支持数字类型,并且必须大于0";
185 | try {
186 | int userCount = Integer.parseInt(optionValue);
187 | if (userCount <= 0) {
188 | throw new ParseException(message);
189 | }
190 | cliArgs.setUserCount(userCount);
191 | } catch (Exception e) {
192 | throw new ParseException(message);
193 | }
194 | }
195 | }
196 | }
197 |
198 | private static void maxItemPerUserOption(CommandLine commandLine, CliArgs cliArgs) throws ParseException {
199 | if (commandLine.hasOption(CliArgs.MAX_ITEM_PER_USER)) {
200 | String optionValue = commandLine.getOptionValue(CliArgs.MAX_ITEM_PER_USER);
201 | if (StringUtils.isNotBlank(optionValue)) {
202 | String message = "选项" + CliArgs.MAX_ITEM_PER_USER + "只支持数字类型,并且必须大于0";
203 | try {
204 | int maxItemPerUser = Integer.parseInt(optionValue);
205 | if (maxItemPerUser <= 0) {
206 | throw new ParseException(message);
207 | }
208 | cliArgs.setMaxItemPerUser(maxItemPerUser);
209 | } catch (Exception e) {
210 | throw new ParseException(message);
211 | }
212 | }
213 | }
214 | }
215 |
216 | private static void exit0() {
217 | System.exit(0);
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/mysql-data-generator/src/main/java/com/wuda/tester/mysql/generate/FoundationBasedDataSet.java:
--------------------------------------------------------------------------------
1 | package com.wuda.tester.mysql.generate;
2 |
3 | import com.wuda.foundation.commons.CreateEmail;
4 | import com.wuda.foundation.commons.CreatePhone;
5 | import com.wuda.foundation.commons.property.CreatePropertyKey;
6 | import com.wuda.foundation.commons.property.CreatePropertyValue;
7 | import com.wuda.foundation.item.CreateItem;
8 | import com.wuda.foundation.item.CreateItemDescription;
9 | import com.wuda.foundation.item.CreateItemGeneral;
10 | import com.wuda.foundation.item.CreateItemVariation;
11 | import com.wuda.foundation.lang.Constant;
12 | import com.wuda.foundation.lang.FoundationContext;
13 | import com.wuda.foundation.lang.identify.BuiltinIdentifierType;
14 | import com.wuda.foundation.lang.identify.LongIdentifier;
15 | import com.wuda.foundation.store.BindStoreUser;
16 | import com.wuda.foundation.store.CreateStore;
17 | import com.wuda.foundation.store.CreateStoreGeneral;
18 | import com.wuda.foundation.user.BindUserEmail;
19 | import com.wuda.foundation.user.BindUserPhone;
20 | import com.wuda.foundation.user.CreateUser;
21 | import com.wuda.foundation.user.CreateUserAccount;
22 | import com.wuda.tester.mysql.cli.CliArgs;
23 | import com.wuda.tester.mysql.commons.utils.RandomUtilsExt;
24 | import lombok.Getter;
25 | import org.apache.commons.lang3.RandomUtils;
26 | import org.apache.commons.lang3.tuple.ImmutablePair;
27 |
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | /**
32 | * 生成foundation项目需要的数据..
33 | *
34 | * @author wuda
35 | */
36 | @Getter
37 | public class FoundationBasedDataSet implements DataSet {
38 |
39 | private int batchSize = 500;
40 | private byte byte0 = (byte) 0;
41 |
42 | private List users;
43 | private List userAccounts;
44 | private List emails;
45 | private List bindUserEmails;
46 | private List phones;
47 | private List bindUserPhones;
48 | private List stores;
49 | private List bindStoreUsers;
50 | private List storeGenerals;
51 | private List items;
52 | private List itemGenerals;
53 | private List itemVariations;
54 | private List itemDescriptions;
55 | private List propertyKeys;
56 | private List propertyValues;
57 |
58 | @Override
59 | public void prepare(CliArgs cliArgs) {
60 | users = newCreateUserList();
61 | userAccounts = newCreateUserAccountList(users);
62 | ImmutablePair, List> userEmail = newUserEmail(users);
63 | emails = userEmail.left;
64 | bindUserEmails = userEmail.right;
65 | ImmutablePair, List> userPhone = newUserPhone(users);
66 | phones = userPhone.left;
67 | bindUserPhones = userPhone.right;
68 | ImmutablePair, List> userStore = newUserStore(users);
69 | stores = userStore.left;
70 | bindStoreUsers = userStore.right;
71 | storeGenerals = newCreateStoreGeneralList(stores);
72 | items = newItemList(stores, cliArgs.getMaxItemPerUser());
73 | itemGenerals = newItemGeneralList(items);
74 | itemVariations = newItemVariationList(items);
75 | itemDescriptions = newItemDescriptionList(items);
76 | propertyKeys = newPropertyKeyList(items);
77 | propertyValues = newPropertyValueList(propertyKeys);
78 | }
79 |
80 | private List newCreateUserList() {
81 | List list = new ArrayList<>(batchSize);
82 | Long[] ids = FoundationContext.getLongKeyGenerator().next(batchSize);
83 | for (int i = 0; i < ids.length; i++) {
84 | long id = ids[i];
85 | CreateUser user = new CreateUser.Builder()
86 | .setId(id)
87 | .setUserType(byte0)
88 | .setUserState(byte0)
89 | .build();
90 | list.add(user);
91 | }
92 | return list;
93 | }
94 |
95 | private List newCreateUserAccountList(List users) {
96 | List list = new ArrayList<>(users.size());
97 | Long[] ids = FoundationContext.getLongKeyGenerator().next(users.size());
98 | for (int i = 0; i < users.size(); i++) {
99 | CreateUser user = users.get(i);
100 | long id = ids[i];
101 | CreateUserAccount userAccount = new CreateUserAccount.Builder()
102 | .setId(id)
103 | .setUserId(user.getId())
104 | .setPassword(RandomUtilsExt.enRandomString(10))
105 | .setUsername(RandomUtilsExt.enRandomString(9))
106 | .setState(byte0)
107 | .build();
108 | list.add(userAccount);
109 | }
110 | return list;
111 | }
112 |
113 | private ImmutablePair, List> newUserEmail(List users) {
114 | List emailList = new ArrayList<>(users.size());
115 | List bindingList = new ArrayList<>(users.size());
116 | Long[] emailIds = FoundationContext.getLongKeyGenerator().next(users.size());
117 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size());
118 | for (int i = 0; i < users.size(); i++) {
119 | CreateUser user = users.get(i);
120 | long emailId = emailIds[i];
121 | CreateEmail email = new CreateEmail.Builder()
122 | .setId(emailId)
123 | .setAddress(RandomUtilsExt.enRandomString(10) + RandomUtilsExt.randomEmailSuffix())
124 | .setEmailState(byte0)
125 | .build();
126 |
127 | long bindingId = bindingIds[i];
128 | BindUserEmail bindUserEmail = new BindUserEmail.Builder()
129 | .setId(bindingId)
130 | .setEmailId(emailId)
131 | .setUserId(user.getId())
132 | .setUse(byte0)
133 | .setState(byte0)
134 | .build();
135 | emailList.add(email);
136 | bindingList.add(bindUserEmail);
137 | }
138 | return new ImmutablePair<>(emailList, bindingList);
139 | }
140 |
141 | private ImmutablePair, List> newUserPhone(List users) {
142 | List phoneList = new ArrayList<>(users.size());
143 | List bindingList = new ArrayList<>(users.size());
144 | Long[] phoneIds = FoundationContext.getLongKeyGenerator().next(users.size());
145 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size());
146 | for (int i = 0; i < users.size(); i++) {
147 | CreateUser user = users.get(i);
148 | long phoneId = phoneIds[i];
149 | CreatePhone phone = new CreatePhone.Builder()
150 | .setId(phoneId)
151 | .setNumber(RandomUtilsExt.enRandomString(11))
152 | .setPhoneType(byte0)
153 | .setPhoneState(byte0)
154 | .build();
155 |
156 | long bindingId = bindingIds[i];
157 | BindUserPhone bindUserPhone = new BindUserPhone.Builder()
158 | .setId(bindingId)
159 | .setPhoneId(phoneId)
160 | .setUserId(user.getId())
161 | .setUse(byte0)
162 | .setState(byte0)
163 | .build();
164 | phoneList.add(phone);
165 | bindingList.add(bindUserPhone);
166 | }
167 | return new ImmutablePair<>(phoneList, bindingList);
168 | }
169 |
170 | private ImmutablePair, List> newUserStore(List users) {
171 | List storeList = new ArrayList<>(users.size());
172 | List bindingList = new ArrayList<>(users.size());
173 | Long[] storeIds = FoundationContext.getLongKeyGenerator().next(users.size());
174 | Long[] bindingIds = FoundationContext.getLongKeyGenerator().next(users.size());
175 | for (int i = 0; i < users.size(); i++) {
176 | CreateUser user = users.get(i);
177 | long storeId = storeIds[i];
178 | CreateStore store = new CreateStore.Builder()
179 | .setId(storeId)
180 | .setStoreType(byte0)
181 | .setStoreState(byte0)
182 | .build();
183 |
184 | long bindingId = bindingIds[i];
185 | BindStoreUser bindStoreUser = new BindStoreUser.Builder()
186 | .setId(bindingId)
187 | .setUserId(user.getId())
188 | .setStoreId(storeId)
189 | .isStoreOwner(true)
190 | .build();
191 | storeList.add(store);
192 | bindingList.add(bindStoreUser);
193 | }
194 | return new ImmutablePair<>(storeList, bindingList);
195 | }
196 |
197 | private List newCreateStoreGeneralList(List stores) {
198 | List list = new ArrayList<>(stores.size());
199 | Long[] ids = FoundationContext.getLongKeyGenerator().next(stores.size());
200 | for (int i = 0; i < stores.size(); i++) {
201 | CreateStore store = stores.get(i);
202 | long id = ids[i];
203 | CreateStoreGeneral storeGeneral = new CreateStoreGeneral.Builder()
204 | .setId(id)
205 | .setStoreId(store.getId())
206 | .setStoreName(RandomUtilsExt.enRandomString(5))
207 | .build();
208 | list.add(storeGeneral);
209 | }
210 | return list;
211 | }
212 |
213 | private List newItemList(List stores, int perStore) {
214 | List list = new ArrayList<>(stores.size());
215 | for (int i = 0; i < stores.size(); i++) {
216 | CreateStore store = stores.get(i);
217 | int actualPerStore = RandomUtils.nextInt(1, perStore);
218 | Long[] ids = FoundationContext.getLongKeyGenerator().next(actualPerStore);
219 | for (int size = 0; size < actualPerStore; size++) { // 为每个店铺生成不同数量的item
220 | long id = ids[size];
221 | CreateItem item = new CreateItem.Builder()
222 | .setId(id)
223 | .setStoreId(store.getId())
224 | .setCategoryId(1L)
225 | .setItemState(byte0)
226 | .setItemType(byte0)
227 | .build();
228 | list.add(item);
229 | }
230 | }
231 | return list;
232 | }
233 |
234 | private List newItemGeneralList(List items) {
235 | List list = new ArrayList<>(items.size());
236 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size());
237 | for (int i = 0; i < items.size(); i++) {
238 | CreateItem item = items.get(i);
239 | long id = ids[i];
240 | CreateItemGeneral itemGeneral = new CreateItemGeneral.Builder()
241 | .setId(id)
242 | .setItemId(item.getId())
243 | .setName(RandomUtilsExt.enRandomString(8))
244 | .build();
245 | list.add(itemGeneral);
246 | }
247 | return list;
248 | }
249 |
250 | private List newItemVariationList(List items) {
251 | List list = new ArrayList<>(items.size());
252 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size());
253 | for (int i = 0; i < items.size(); i++) {
254 | CreateItem item = items.get(i);
255 | long id = ids[i];
256 | CreateItemVariation itemVariation = new CreateItemVariation.Builder()
257 | .setId(id)
258 | .setItemId(item.getId())
259 | .setName(RandomUtilsExt.enRandomString(8))
260 | .setState(byte0)
261 | .build();
262 | list.add(itemVariation);
263 | }
264 | return list;
265 | }
266 |
267 | private List newItemDescriptionList(List items) {
268 | List list = new ArrayList<>(items.size());
269 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size());
270 | for (int i = 0; i < items.size(); i++) {
271 | CreateItem item = items.get(i);
272 | long id = ids[i];
273 | CreateItemDescription itemDescription = new CreateItemDescription.Builder()
274 | .setId(id)
275 | .setItemId(item.getId())
276 | .setItemVariationId(Constant.NOT_EXISTS_ID)
277 | .setContent(RandomUtilsExt.enRandomString(8))
278 | .build();
279 | list.add(itemDescription);
280 | }
281 | return list;
282 | }
283 |
284 | private List newPropertyKeyList(List items) {
285 | List list = new ArrayList<>(items.size());
286 | Long[] ids = FoundationContext.getLongKeyGenerator().next(items.size());
287 | for (int i = 0; i < items.size(); i++) {
288 | CreateItem item = items.get(i);
289 | long id = ids[i];
290 | CreatePropertyKey createPropertyKey = new CreatePropertyKey.Builder()
291 | .setId(id)
292 | .setOwner(new LongIdentifier(item.getId(), BuiltinIdentifierType.TABLE_ITEM))
293 | .setType(byte0)
294 | .setUse(byte0)
295 | .setKey(RandomUtilsExt.enRandomString(6))
296 | .build();
297 | list.add(createPropertyKey);
298 | }
299 | return list;
300 | }
301 |
302 | private List newPropertyValueList(List propertyKeys) {
303 | List list = new ArrayList<>(propertyKeys.size());
304 | Long[] ids = FoundationContext.getLongKeyGenerator().next(propertyKeys.size());
305 | for (int i = 0; i < propertyKeys.size(); i++) {
306 | CreatePropertyKey propertyKey = propertyKeys.get(i);
307 | long id = ids[i];
308 | CreatePropertyValue createPropertyValue = new CreatePropertyValue.Builder()
309 | .setId(id)
310 | .setPropertyKeyId(propertyKey.getId())
311 | .setValue(RandomUtilsExt.enRandomString(8))
312 | .build();
313 | list.add(createPropertyValue);
314 | }
315 | return list;
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/mysql_tester.sql:
--------------------------------------------------------------------------------
1 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
2 | /*!40101 SET NAMES utf8 */;
3 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
4 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
5 |
6 | CREATE DATABASE IF NOT EXISTS `foundation_commons`;
7 | USE `foundation_commons`;
8 |
9 | CREATE TABLE IF NOT EXISTS `email` (
10 | `email_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
11 | `address` varchar(20) NOT NULL COMMENT 'email address',
12 | `state` tinyint(3) unsigned NOT NULL COMMENT '邮箱状态。1比如:验证不通过,验证通过,未验证',
13 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
14 | `create_user_id` bigint(20) unsigned NOT NULL,
15 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
16 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
17 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
18 | PRIMARY KEY (`email_id`),
19 | UNIQUE KEY `idx_email_unique` (`address`,`is_deleted`)
20 | ) ENGINE=InnoDB AUTO_INCREMENT=314876056249048299 COMMENT='email信息';
21 |
22 | CREATE TABLE IF NOT EXISTS `phone` (
23 | `phone_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
24 | `number` varchar(20) NOT NULL COMMENT '电话号码',
25 | `type` tinyint(3) unsigned NOT NULL COMMENT '电话类型。用于区分手机,-固话等。注意:不是用来区分【客服电话还是400电话】这种类型,这些属于业务,应该由具体的业务表关联到这个表。',
26 | `state` tinyint(3) unsigned NOT NULL COMMENT '手机状态',
27 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
28 | `create_user_id` bigint(20) unsigned NOT NULL,
29 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
30 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
31 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
32 | PRIMARY KEY (`phone_id`),
33 | UNIQUE KEY `idx_phone_number` (`number`,`is_deleted`)
34 | ) ENGINE=InnoDB AUTO_INCREMENT=314875826682205361 COMMENT='电话信息。';
35 |
36 | CREATE TABLE IF NOT EXISTS `property_key` (
37 | `property_key_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
38 | `key` varchar(45) NOT NULL COMMENT '属性名',
39 | `type` tinyint(3) unsigned NOT NULL COMMENT '属性的类型,比如最常用的就是“字面量”类型;比如该属性表示图片,属性值保存图片的链接;比如该属性表示颜色,因为在一些应用中,可以使用调色盘选取颜色,或者在显示时,可以显示颜色,而不是白色这样的纯文本',
40 | `owner_type` tinyint(3) unsigned NOT NULL COMMENT '该属性的owner的类型',
41 | `owner_identifier` bigint(20) unsigned NOT NULL COMMENT '该属性的owner的id',
42 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
43 | `create_user_id` bigint(20) unsigned NOT NULL,
44 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
45 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
46 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
47 | PRIMARY KEY (`property_key_id`),
48 | UNIQUE KEY `idx_unique` (`owner_identifier`,`owner_type`,`key`,`is_deleted`)
49 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986349204186 COMMENT='属性的key';
50 |
51 | CREATE TABLE IF NOT EXISTS `property_value` (
52 | `property_value_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
53 | `property_key_id` bigint(20) unsigned NOT NULL COMMENT '所属的key',
54 | `value` varchar(45) NOT NULL COMMENT '属性值',
55 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
56 | `create_user_id` bigint(20) unsigned NOT NULL,
57 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
58 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
59 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
60 | PRIMARY KEY (`property_value_id`),
61 | UNIQUE KEY `idx_attr_value` (`property_key_id`,`value`,`is_deleted`)
62 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986353399087 COMMENT='属性的值';
63 |
64 | CREATE DATABASE IF NOT EXISTS `foundation_item`;
65 | USE `foundation_item`;
66 |
67 | CREATE TABLE IF NOT EXISTS `item` (
68 | `item_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
69 | `store_id` bigint(20) unsigned NOT NULL COMMENT '所属店铺ID',
70 | `type` tinyint(3) unsigned NOT NULL COMMENT '商品类型 . 不同类型的商品, 保存到各自不同的表中. 参考 https://learnwoo.com/woocommerce-different-product-types/',
71 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态',
72 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
73 | `create_user_id` bigint(20) unsigned NOT NULL,
74 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
75 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
76 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
77 | PRIMARY KEY (`item_id`),
78 | KEY `fk_store_id` (`store_id`)
79 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986730886848 COMMENT='代表所有的物品,之前有把用户ID放进来,表示该物品所属的用户,但是考虑到如果有子账号的情况,物品难道属于这个子账号所属的用户吗?而且记录了创建人用户ID,考虑这两个因素,因此不设置用户ID列';
80 |
81 | CREATE TABLE IF NOT EXISTS `item_description` (
82 | `item_description_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
83 | `item_id` bigint(20) unsigned NOT NULL,
84 | `item_variation_id` bigint(20) unsigned NOT NULL DEFAULT '0',
85 | `content` mediumtext COMMENT '描述内容',
86 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
87 | `create_user_id` bigint(20) unsigned NOT NULL,
88 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
89 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
90 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
91 | PRIMARY KEY (`item_description_id`),
92 | KEY `fk_item_id` (`item_id`),
93 | KEY `fk_item_variation_id` (`item_variation_id`)
94 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986345009285 COMMENT='item的描述信息,通常作为详情的一个字段,但是,由于描述信息通常内容较多,很多orm框架都是select *,分开了可以避免查询出来(有时候根本就没用到),而且大数据量的字段更新性能较差,如果需要更新,会影响到核心item表,因此单独作为一个表保存。也可以表示物品某个规格的描述信息,如果variation id不等于0';
95 |
96 | CREATE TABLE IF NOT EXISTS `item_general` (
97 | `item_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
98 | `item_id` bigint(20) unsigned NOT NULL,
99 | `name` varchar(45) NOT NULL,
100 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
101 | `create_user_id` bigint(20) unsigned NOT NULL,
102 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
103 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
104 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
105 | PRIMARY KEY (`item_general_id`),
106 | KEY `fk_item_id` (`item_id`)
107 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986571504463 COMMENT='物品基本信息,也可以表示物品某个规格的基本信息,如果variation id不等于0';
108 |
109 | CREATE TABLE IF NOT EXISTS `item_variation` (
110 | `item_variation_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
111 | `item_id` bigint(20) unsigned NOT NULL,
112 | `name` varchar(45) NOT NULL DEFAULT '' COMMENT '规格名称。方便管理。',
113 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态',
114 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
115 | `create_user_id` bigint(20) unsigned NOT NULL,
116 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
117 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
118 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
119 | PRIMARY KEY (`item_variation_id`),
120 | KEY `fk_item_id` (`item_id`)
121 | ) ENGINE=InnoDB AUTO_INCREMENT=314874986575699859 COMMENT='规格。比如一件衣服,有红色,白色两种规格。具体的属性和值保存在MongoDB. 不能用属性ID关联, 而是要具体的属性名称和值, 避免关联的属性修改. 注意和SKU之间的区别.';
122 |
123 | CREATE DATABASE IF NOT EXISTS `foundation_store`;
124 | USE `foundation_store`;
125 |
126 | CREATE TABLE IF NOT EXISTS `store` (
127 | `store_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
128 | `type` tinyint(3) unsigned NOT NULL COMMENT '店铺类型',
129 | `state` tinyint(3) unsigned NOT NULL COMMENT '店铺状态',
130 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
131 | `create_user_id` bigint(20) unsigned NOT NULL,
132 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
133 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
134 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
135 | PRIMARY KEY (`store_id`)
136 | ) ENGINE=InnoDB AUTO_INCREMENT=314875818503314715 COMMENT='店铺信息';
137 |
138 | CREATE TABLE IF NOT EXISTS `store_general` (
139 | `store_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
140 | `store_id` bigint(20) unsigned NOT NULL COMMENT '主键',
141 | `store_name` varchar(45) NOT NULL COMMENT '店铺名称',
142 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
143 | `create_user_id` bigint(20) unsigned NOT NULL,
144 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
145 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
146 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
147 | PRIMARY KEY (`store_general_id`),
148 | KEY `fk_store_id` (`store_id`)
149 | ) ENGINE=InnoDB AUTO_INCREMENT=314875810760626565 COMMENT='店铺基本信息';
150 |
151 | CREATE TABLE IF NOT EXISTS `store_user_relationship` (
152 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
153 | `store_id` bigint(20) unsigned NOT NULL COMMENT 'store id',
154 | `user_id` bigint(20) unsigned NOT NULL COMMENT 'user id',
155 | `is_store_owner` bit(1) NOT NULL COMMENT '该用户是否店铺的owner。一个店铺只能有一个owner,就好像在store表中放入user id字段,表明一对一的关系一样。',
156 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
157 | `create_user_id` bigint(20) unsigned NOT NULL,
158 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
159 | PRIMARY KEY (`id`),
160 | KEY `fk_store_id` (`store_id`),
161 | KEY `fk_user_id` (`user_id`)
162 | ) ENGINE=InnoDB AUTO_INCREMENT=314875816137724979 COMMENT='如果把用户ID字段放在store表中,表明店铺属于某个用户,但是如果有多个用户可以管理这个店铺呢?有种做法是一个用户作为另一个用户的子账号;也可以建立用户与店铺的关联关系,这样感觉更符合逻辑。把用户IID放在store表,可以很明确的表明店铺的owner,如果是用关联关系表的话,就需要明确的标明哪个用户是owner,哪些用户只是管理这个店铺的。';
163 |
164 | CREATE DATABASE IF NOT EXISTS `foundation_user` ;
165 | USE `foundation_user`;
166 |
167 | CREATE TABLE IF NOT EXISTS `individual_user_general` (
168 | `individual_user_general_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
169 | `user_id` bigint(20) unsigned NOT NULL,
170 | `nickname` varchar(20) DEFAULT NULL COMMENT '昵称',
171 | `biography` varchar(128) DEFAULT NULL COMMENT '个人简介。简短的介绍',
172 | `picture` varchar(45) DEFAULT NULL COMMENT '用户图像。保存的是图片地址。命名来源:github',
173 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
174 | `create_user_id` bigint(20) unsigned NOT NULL,
175 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
176 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
177 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
178 | PRIMARY KEY (`individual_user_general_id`),
179 | KEY `fk_user_id` (`user_id`)
180 | ) ENGINE=InnoDB COMMENT='个人用户-基本信息';
181 |
182 | CREATE TABLE IF NOT EXISTS `user` (
183 | `user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
184 | `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户类型',
185 | `state` tinyint(3) unsigned NOT NULL COMMENT '用户状态',
186 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
187 | `create_user_id` bigint(20) unsigned NOT NULL,
188 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
189 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
190 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
191 | PRIMARY KEY (`user_id`)
192 | ) ENGINE=InnoDB AUTO_INCREMENT=314876148024612889 COMMENT='用户有很多类型,比如一种分类方法是把用户分成个人用户和企业用户,而不同类型的用户需要的字段不一样,但是他们都是用户,即 is-a user。这个表属于所有用户的基本信息,其他不同类型的用户有自己专属的表,然后用用户ID关联回这个表。这样做还有一个好处,那就是其他表中的用户ID都统一关联回这个表,这样用户ID就不会有歧义了。';
193 |
194 | CREATE TABLE IF NOT EXISTS `user_account` (
195 | `user_account_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
196 | `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
197 | `username` varchar(45) NOT NULL COMMENT '只能是英文模式下的字母,数字,下划线,中划线,必须明确检查保证不是邮箱。设置以后不能修改(github可以修改),可用作用户主页URL的一部分,参考github。注意和昵称的区别',
198 | `password` varchar(45) NOT NULL COMMENT '只能是ASCII表中的可打印字符',
199 | `state` tinyint(3) unsigned NOT NULL COMMENT '账号的状态',
200 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
201 | `create_user_id` bigint(20) unsigned NOT NULL,
202 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
203 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
204 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
205 | PRIMARY KEY (`user_account_id`),
206 | UNIQUE KEY `idx_username` (`username`,`is_deleted`)
207 | ) ENGINE=InnoDB AUTO_INCREMENT=314876128948918487 COMMENT='用户账号信息,适用各种类型的用户';
208 |
209 | CREATE TABLE IF NOT EXISTS `user_email` (
210 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
211 | `user_id` bigint(20) unsigned NOT NULL,
212 | `email_id` bigint(20) unsigned NOT NULL,
213 | `use` tinyint(3) unsigned NOT NULL COMMENT '用途。比如用于登录',
214 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态,每种用途的email状态可能不同,比如如果用于登录的email,状态可以是禁止登录状态',
215 | `description` varchar(45) DEFAULT NULL COMMENT '简单描述',
216 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
217 | `create_user_id` bigint(20) unsigned NOT NULL,
218 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
219 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
220 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
221 | PRIMARY KEY (`id`),
222 | UNIQUE KEY `idx_unique` (`email_id`,`use`,`is_deleted`),
223 | KEY `fk_user_id` (`user_id`)
224 | ) ENGINE=InnoDB AUTO_INCREMENT=314875924648564415 COMMENT='用户的email';
225 |
226 | CREATE TABLE IF NOT EXISTS `user_phone` (
227 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
228 | `user_id` bigint(20) unsigned NOT NULL,
229 | `phone_id` bigint(20) unsigned NOT NULL,
230 | `use` tinyint(3) unsigned NOT NULL COMMENT '电话的用途。比如用于400电话。也就是电话使用的业务场景。',
231 | `state` tinyint(3) unsigned NOT NULL COMMENT '状态,每种用途的phone的il状态可能不同,比如如果用于登录的phone,状态可以是禁止登录状态',
232 | `description` varchar(45) DEFAULT NULL COMMENT '简单描述',
233 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
234 | `create_user_id` bigint(20) unsigned NOT NULL,
235 | `last_modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
236 | `last_modify_user_id` bigint(20) unsigned NOT NULL,
237 | `is_deleted` bigint(20) unsigned NOT NULL DEFAULT '0',
238 | PRIMARY KEY (`id`),
239 | UNIQUE KEY `idx_unique` (`phone_id`,`use`,`is_deleted`),
240 | KEY `fk_user_id` (`user_id`)
241 | ) ENGINE=InnoDB AUTO_INCREMENT=314875826682205861 COMMENT='用户的电话';
242 |
243 | /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
244 | /*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
245 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
--------------------------------------------------------------------------------