【DB系列】数据库初始化-DataSourceInitializer方式

文章目录
  1. I. 项目搭建
    1. 1. 依赖
    2. 2. 配置
  2. II. 初始化
    1. 1.初始化配置
    2. 2.验证
    3. 3. 小结
  3. III. 不能错过的源码和相关知识点
    1. 0. 项目
    2. 1. 微信公众号: 一灰灰Blog

前面介绍的两篇基于配置方式的数据库初始化方式,使用起来非常简单,但是有一个非常明显的问题,如何实现表结构存在时不再初始化,不存在时才执行? 如果数据库也不存在,也需要初始化时创建,可行么?

接下来介绍一下如何使用DataSourceInitializer来实现自主可控的数据初始化

I. 项目搭建

1. 依赖

首先搭建一个标准的SpringBoot项目工程,相关版本以及依赖如下

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>

本文使用MySql数据库, 版本8.0.31

2. 配置

注意实现初始化数据库表操作的核心配置就在下面,重点关注

配置文件: resources/application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 默认的数据库名
database:
name: story

spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/${database.name}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password:


logging:
level:
root: info
org:
springframework:
jdbc:
core: debug

注意上面的配置,我们新定义了一个数据库的配置项 database.name, 主要是为了检测database是否存在,若不存在时,创建对应的数据库时使用

接下来是初始化sql脚本

resources/init-schema.sql 对应的初始化ddl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `user`
(
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`third_account_id` varchar(128) NOT NULL DEFAULT '' COMMENT '第三方用户ID',
`user_name` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码',
`login_type` tinyint NOT NULL DEFAULT '0' COMMENT '登录方式: 0-微信登录,1-账号密码登录',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
PRIMARY KEY (`id`),
KEY `key_third_account_id` (`third_account_id`),
KEY `user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户登录表';

resources/init-data.sql 对用的初始化dml

1
2
INSERT INTO `user` (id, third_account_id, `user_name`, `password`, login_type, deleted)
VALUES (1, 'a7cb7228-0f85-4dd5-845c-7c5df3746e92', 'admin', 'admin', 0, 0);

II. 初始化

1.初始化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Slf4j
@Configuration
public class DataSourceInit {

/**
* 初始化的表结构语句
*/
@Value("classpath:init-schema.sql")
private Resource schemaSql;
/**
* 初始化数据
*/
@Value("classpath:init-data.sql")
private Resource initData;

@Value("${database.name}")
private String database;

@Bean
public DataSourceInitializer dataSourceInitializer(final DataSource dataSource) {
final DataSourceInitializer initializer = new DataSourceInitializer();
// 设置数据源
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator());
// true表示需要执行,false表示不需要初始化
initializer.setEnabled(needInit(dataSource));
return initializer;
}

private DatabasePopulator databasePopulator() {
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(schemaSql);
populator.addScripts(initData);
populator.setSeparator(";");
return populator;
}

// 省略 needInit方法
}

我们这里主要是借助 DataSourceInitializer 来实现初始化,其核心有两个配置

  • DatabasePopulator: 通过addScripts来指定对应的sql文件
  • DataSourceInitializer#setEnabled: 判断是否需要执行初始化

接下来重点需要看的就是needInit方法,我们再这个方法里面,需要判断数据库是否存在,若不存在时,则创建数据库;然后再判断表是否存在,以此来决定是否需要执行初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 检测一下数据库中表是否存在,若存在则不初始化;否则基于 init-schema.sql 进行初始化表
*
* @param dataSource
* @return
*/
private boolean needInit(DataSource dataSource) {
if (autoInitDatabase()) {
return true;
}
// 根据是否存在表来判断是否需要执行sql操作
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List list = jdbcTemplate.queryForList("SELECT table_name FROM information_schema.TABLES where table_name = 'user' and table_schema = '" + database + "';");
boolean init = CollectionUtils.isEmpty(list);
if (init) {
log.info("库表不存在,执行建表及数据初始化");
} else {
log.info("表结构已存在,无需初始化");
}
return init;
}


/**
* 数据库不存在时,尝试创建数据库
*/
private boolean autoInitDatabase() {
// 查询失败,可能是数据库不存在,尝试创建数据库之后再次测试
URI url = URI.create(SpringUtil.getConfig("spring.datasource.url").substring(5));
String uname = SpringUtil.getConfig("spring.datasource.username");
String pwd = SpringUtil.getConfig("spring.datasource.password");
try (Connection connection = DriverManager.getConnection("jdbc:mysql://" + url.getHost() + ":" + url.getPort() +
"?useUnicode=true&characterEncoding=UTF-8&useSSL=false", uname, pwd);
Statement statement = connection.createStatement()) {
ResultSet set = statement.executeQuery("select schema_name from information_schema.schemata where schema_name = '" + database + "'");
if (!set.next()) {
// 不存在时,创建数据库
String createDb = "CREATE DATABASE IF NOT EXISTS " + database;
connection.setAutoCommit(false);
statement.execute(createDb);
connection.commit();
log.info("创建数据库({})成功", database);
if (set.isClosed()) {
set.close();
}
return true;
}
set.close();
log.info("数据库已存在,无需初始化");
return false;
} catch (SQLException e2) {
throw new RuntimeException(e2);
}
}

上面的数据库判断是否存在以及初始化的过程相对基础,直接使用了基础的Connection进行操作;这里借助了SpringUtil来获取配置信息,对应的类源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.git.hui.schema.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
* @author YiHui
* @date 2022/12/9
*/
@Component
public class SpringUtil implements ApplicationContextAware, EnvironmentAware {
private static ApplicationContext context;
private static Environment environment;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.context = applicationContext;
}

@Override
public void setEnvironment(Environment environment) {
SpringUtil.environment = environment;
}

/**
* 获取bean
*
* @param bean
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> bean) {
return context.getBean(bean);
}

public static Object getBean(String beanName) {
return context.getBean(beanName);
}

/**
* 获取配置
*
* @param key
* @return
*/
public static String getConfig(String key) {
return environment.getProperty(key);
}

/**
* 发布事件消息
*
* @param event
*/
public static void publishEvent(ApplicationEvent event) {
context.publishEvent(event);
}
}

到此整个初始化相关的配置已经完成;接下来我们验证一下

2.验证

再项目启动成功之后,查看一下数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@SpringBootApplication
public class Application implements ApplicationRunner {
@Autowired
private JdbcTemplate jdbcTemplate;

public static void main(String[] args) {
SpringApplication.run(Application.class);
}

@Override
public void run(ApplicationArguments args) throws Exception {
List list = jdbcTemplate.queryForList("select * from user limit 2");
log.info("启动成功,初始化数据: {}\n{}", list.size(), list);
}
}

3. 小结

本文主要介绍的是基于DataSourceInitializer来实现自主可控的数据初始化,其核心配置为

  • DatabasePopulator: 通过addScripts来指定对应的sql文件
  • DataSourceInitializer#setEnabled: 判断是否需要执行初始化

此外本文还介绍了如何判断数据库是否存在,当数据库不存在时,借助基础的Connection来建立连接,创建数据库;从初始化角度来看,这几篇文中介绍的方式已经足够,但是在项目制的场景下,我们需要记录数据库的版本迭代记录,下一篇将介绍如何使用liquibase来实现数据版本管理,解决初始化以及增量的迭代变更

III. 不能错过的源码和相关知识点

0. 项目

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到