Runner 项目默认数据初始化

本文介绍了如何在SpringBoot项目中利用Flyway进行数据库初始化,包括创建表和插入测试数据。通过配置Flyway并结合CSV文件,实现了灵活的数据操作和默认值初始化。同时,文章展示了如何通过命令行参数执行不同的数据库操作,如创建表、插入数据和读取CSV文件预埋数据。
摘要由CSDN通过智能技术生成

1、简介

在实际的项目开发中,我们对应数据库的操作是频繁的。因为在项目之初我们是不太可能把数据库设计的非常完美,所有导致我们需要频繁操作数据库,对于测试数据的我们也需要保留。如果重新手动创建数据库那会拖延项目的开发进度,本篇文章从项目的角度来初始换数据库,并可以保留数据库的测试数据,还可以对一些必要的默认数据进行预埋处理。

2、准备工作

环境配置:

  • JDK 8
  • Windows 10
  • Mysql 8 (Mysql 数据库版本要在5.7以上)
  • 开发工具 IDEA

项目的架构(maven项目):SpringBoot

使用的技术:Flyway,CSV,Mybatis-plus

3、数据库表的创建

创建数据表的方式有很多,比如:使用JPA、Hibernate、mybatis等等,在或者也可以手动创建表,但是这些方法操作起来都比较麻烦而且无法记录数据库迭代的信息若想回退版本的话比较难。

既然我们使用runner初始化数据库,当然需要记录历史版本信息,这也符合项目开发的需要。所有在这里使用Flyway来对数据进行操作。

Flyway的使用:https://blog.csdn.net/WQB_123/article/details/121143428?spm=1001.2014.3001.5501

上面这篇博客简单的介绍了一下Flyway的使用方法,我们可以在sql文件中写关于数据的任何SQL语句。但对于一些默认数据的插入会导致大量的SQL代码的冗余。所有下面对于Flyway的操作更加灵活,方式也不一样。

3.1 代码配置设置

导入依赖(pom.xml)

<!-- https://mvnrepository.com/artifact/org.flywaydb/flyway-core -->
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
  <version>8.0.2</version>
</dependency>

springbooot配置(application.yml)

spring:
  jpa:
    hibernate:
      ddl-auto: none  
    show-sql: false
    generate-ddl: false
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

  datasource:
    url: jdbc:mysql://localhost:3306/runner?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

启动类代码

@SpringBootApplication(exclude = FlywayAutoConfiguration.class)
public class RunnerApplication implements ApplicationRunner {

    private static ApplicationArguments args = null;

    @Autowired
    private MigrateDB migrateDB;

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(RunnerApplication.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        app.run(args).close();
    }

    @Override
    public void run(ApplicationArguments arguments) throws IOException {
      // 获取启动时探针传入的值
        args = arguments;
        List<String> actions = args.getNonOptionArgs();
        RunPointer runPointer = RunPointer.findEnumBydName(actions.get(0));

      //不为空后,进行匹配
        switch (Objects.requireNonNull(runPointer)) {
            case ACTION_DB_MIGRATE:
                if (args.containsOption(RunPointer.ACTION_OPTION_INPUT_PATH.name)) {
                    migrateDB.execute(getOptionValue(RunPointer.ACTION_OPTION_INPUT_PATH.name));
                } else {
                    printError("Please specify options: inputPath");
                }
                break;

            default:
                System.out.println("Options is null ,please specify options!");
                break;
        }

    }
		// 根据field获取指定的值
    private String getOptionValue(String field) {
        List<String> list = args.getOptionValues(field);
        if (list.size() == 0) {
            printError("Empty option for " + field);
            return "";
        } else {
            return list.get(0);
        }
    }

    private void printError(String message) z{
        System.err.println(message);
    }
}

这样改造启动类之后,就可以使用探针的形式指定运行那个程序了。

MigrateDB 代码

@Service
public class MigrateDB {
		// 获取yml中的数据库信息
    @Qualifier(value = "dataSource")
    @Autowired
    private DataSource dataSource;

  //这个方法主要是手动设置flyway启动的参数,并执行flyway操作
    public void execute(String inputFilePath){
        Flyway flyway = Flyway.configure()
                .dataSource(dataSource)
                .locations(inputFilePath)
                .baselineOnMigrate(true)
                .load();
        flyway.migrate();
    }
}

RunPointer 这是启动时探针输入的枚举值

@ToString
@AllArgsConstructor
public enum RunPointer {
    ACTION_DB_MIGRATE("db-migrate"),

    ACTION_OPTION_INPUT_PATH("input"),

    public String name;

  // 获取枚举的键值
    public static RunPointer findEnumBydName(String name) {
        RunPointer[] result = RunPointer.values();
        for (RunPointer p : result) {
            if (p.name.equals(name)) {
                return p;
            }
        }
        return null;
    }
}

sql文件

--V1.0.0.0__createUser.sql
create table user (id bigint not null auto_increment, name varchar(50) not null,primary key (id));

--V1.0.0.1__addUser.sql
insert into user (name) values ("test");

--V1.0.0.2__addUser_tset.sql
insert into user (name) values ("test1");

处理玩这些之后就可以正常使用flyway去创建数据库表了

3.2 启动与验证

启动方式

首先需要设置探针,然后在启动代码;

在这里插入图片描述

验证

启动成功后,取到数据库,可以看到两张表:

在这里插入图片描述

4、默认值初始化

当数据库需要插入一些默认值时,可以使用Flyway去实现,但是会造成数据代码的冗余,而且不方便修改。一下是我用文件解析的方式去执行相应的的文件来初始化数据库。

原理和上面的方式差不多,也是通过探针的方式一样,执行特殊的类方法,从而达目的。

后期也可以自己编写.sh来通过探针逐个启动程序,部署也是一样的。在这里我就不一一讲解了。

下面是所有代码的展示:

4.1目录结构

在这里插入图片描述

4.2 相应文件代码

从上到下

MigrateDB

@Service
public class MigrateDB {

  	// 获取yml配置文件中的数据库配置
    @Qualifier(value = "dataSource")
    @Autowired
    private DataSource dataSource;

  	// 初始化并执行Flyway
    public void execute(String inputFilePath){
        Flyway flyway = Flyway.configure()
                .dataSource(dataSource)
                .locations(inputFilePath)
                .baselineOnMigrate(true)
                .load();
        flyway.migrate();
    }
}

SetupUser

@Service
public class SetupUser {
    @Autowired
    private UserMapper userMapper;

    public void execute(String inputFilePath, Boolean purgeFirst) throws IOException {
      	// 判断是否需要删除数据库原有的数据;它是通过探针传进来的,可以灵活使用。
        if (purgeFirst) {
            userMapper.deleteUser();
            System.out.println("User have been purged!");
        }
				// 获取文件路径,并判断是否为空
        Path inputPath;
        if (inputFilePath != null) {
            Path path = Paths.get(inputFilePath);
            if (!Files.isExecutable(path)) {
                System.out.println("File " + inputFilePath + " does not exist");
                return;
            }
            inputPath = path;

          	// 通过路径解析CSV文件
            CSVFormat csvFormat = CSVFormat.DEFAULT
                    .withCommentMarker('#')
                    .withFirstRecordAsHeader()
                    .withIgnoreEmptyLines()
                    .withIgnoreSurroundingSpaces();
            List<CSVRecord> records = CSVParser.parse(Files.newBufferedReader(inputPath), csvFormat)
                    .getRecords();

            int success = 0;

          	// 根据第一行的规则,逐行获取解析的数据
            for (CSVRecord record : records) {
                if (record.size() == 2) {
                    userMapper.insert(new user(Integer.parseInt(record.get(0)), record.get(1)));
                    success++;
                    System.out.println("Added user:" + record.get(0) + record.get(1));
                } else {
                    System.out.println("Record does not have 3 columns, skipping: " + record.size());
                }
            }
            System.out.println("Added " + success / records.size() + " user from file");
        }
    }
} 

ShowHelp

@Service
public class ShowHelp {
  	// 探针提示文件
    public void execute() {
        System.out.println(
                "db-migrate                      Database migration\n" +
                "--input=[filepath]              Sql file\n" +
                "\n" +
                "setup-user                     Setup user message\n" +
                "--input=[filepath]              Input file in CSV format (optional)\n" +
                "--purge=[true | false]          Purge existing records first"
        );
    }
}

RunPointer

@ToString
@AllArgsConstructor
public enum RunPointer {
    ACTION_DB_MIGRATE("db-migrate"),
    ACTION_SETUP_USER("setup-user"),

    ACTION_OPTION_INPUT_PATH("input"),
    ACTION_OPTION_PURGE("purge");

    public String name;

    public static RunPointer findEnumBydName(String name) {
        RunPointer[] result = RunPointer.values();
        for (RunPointer p : result) {
            if (p.name.equals(name)) {
                return p;
            }
        }
        return null;
    }
}

user

@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
    private Integer id;
    private String name;
}

UserMapper

@Mapper
public interface UserMapper extends BaseMapper<user> {

    //清空指定表
    @Update("truncate table user")
    void deleteUser();
}

RunnerApplication(启动类)

@SpringBootApplication(exclude = FlywayAutoConfiguration.class)
public class RunnerApplication implements ApplicationRunner {

    private static ApplicationArguments args = null;

    @Autowired
    private MigrateDB migrateDB;

    @Autowired
    private ShowHelp showHelp;

    @Autowired
    private SetupUser setupUser;

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(RunnerApplication.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        // 启动完成后关闭;
        app.run(args).close();
    }

		// 启动时根据探针中的内容进行运行相应的程序
    @Override
    public void run(ApplicationArguments arguments) throws IOException {
        args = arguments;
        List<String> actions = args.getNonOptionArgs();
        RunPointer runPointer = RunPointer.findEnumBydName(actions.get(0));

        switch (Objects.requireNonNull(runPointer)) {
            case ACTION_DB_MIGRATE:
                if (args.containsOption(RunPointer.ACTION_OPTION_INPUT_PATH.name)) {
                    migrateDB.execute(getOptionValue(RunPointer.ACTION_OPTION_INPUT_PATH.name));
                } else {
                    printError("Please specify options: inputPath");
                }
                break;
            case ACTION_SETUP_USER:
                Boolean whetherPath = args.containsOption(RunPointer.ACTION_OPTION_INPUT_PATH.name);
                Boolean whetherPurge = args.containsOption(RunPointer.ACTION_OPTION_PURGE.name);
                if (whetherPath && whetherPurge) {
                    String inputPath = getOptionValue(RunPointer.ACTION_OPTION_INPUT_PATH.name);
                    Boolean purge = Boolean.parseBoolean(getOptionValue(RunPointer.ACTION_OPTION_PURGE.name));
                    setupUser.execute(inputPath, purge);
                } else {
                    printError("Please specify options: inputPath , purge");
                }
                break;

            default:
                showHelp.execute();
                System.out.println("Options is null ,please specify options!");
                break;
        }

    }

    private String getOptionValue(String field) {
        List<String> list = args.getOptionValues(field);
        if (list.size() == 0) {
            printError("Empty option for " + field);
            return "";
        } else {
            return list.get(0);
        }
    }

    private void printError(String message) {
        System.err.println(message);
    }
}

db/migration

-- V1.0.0.0__createUser.sql
create table user (id bigint not null auto_increment, name varchar(50) not null,primary key (id));

-- V1.0.0.1__addUser.sql
insert into user (name) values ("test");

-- V1.0.0.2__addUser_tset.sql
insert into user (name) values ("test1");

user.csv

"id","name"
"15","Tom"
"16","zhangSha"
"17","ke"
"18","Aou"

application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: false
    generate-ddl: false
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

  datasource:
    url: jdbc:mysql://localhost:3306/runner?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.flywaydb/flyway-core -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>8.0.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>

        <!-- mybatis-plus 它是自己开发的,非官方的 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.8</version>
        </dependency>
</dependencies>

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>8.0.1</version>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.wqb.mybatisplus.MybatisPlusApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
</build>
4.3 启动方式

该程序的启动方式和上面Flyway的启动方式一样,也是通过探针的方式启动的。

setup-user --input=F:\java\runner\src\main\resources\fixtures\user.csv --purge=false

这个是通过csv预埋数据的探针。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值