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.youkuaiyun.com/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预埋数据的探针。