SpringBoot+Mybatis项目支持自动初始化数据库
引言
需要用到该技术点背景: 本身笔者从事物联网相关开发,项目需要频繁在多个环境下部署。手动导入数据库脚本过于繁琐,本身Mybatis的ORM属性不够完整,故做此调研以减少重复工作量。本文参考了其他文章,主要是发现需要控制实例初始化顺序的关系。
- 本身Mybatis是一个数据库操作工具集定义为半ORM,不属于全ORM。所以不能支持自动初始化数据库,如果使用JPA+Hibernate是可以原生支持自动生成数据库的。
- 解决自动初始化数据库,需要预先准备好待初始化的SQL脚本。本质原理是在Spring容器初始化完毕前,使用数据库驱动连接数据库并检查数据库是否存在,不存在则执行预先准备好的SQL脚本进行数据库初始化。
- 要解决的问题
- 创建数据库初始化类
- 保证数据库初始化类的执行顺序是在数据库连接池初始化之前
- 手动配置数据库连接池的Bean(用于控制初始化顺序)
正文
实现步骤: 1.创建数据库初始化类 注意“/sql/init.sql” 这个路径是指向Resource目录的相对路径。所以你的初始化SQL需要放在Resource目录,便于一起打包到JAR中。
@Slf4j
@Configuration
public class DatabaseInitialize {
/**
* 读取连接地址
*/
@Value("${spring.datasource.url}")
private String url;
/**
* 读取用户名
*/
@Value("${spring.datasource.username}")
private String username;
/**
* 读取密码
*/
@Value("${spring.datasource.password}")
private String password;
/**
* 检测当前连接的库是否存在(连接URL中的数据库)
*
* @return 当前连接的库是否存在
*/
private boolean currentDatabaseExists() {
// 尝试以配置文件中的URL建立连接
try {
Connection connection = DriverManager.getConnection(url, username, password);
connection.close();
} catch (SQLException e) {
// 若连接抛出异常则说明连接URL中指定数据库不存在
return false;
}
// 正常情况下说明连接URL中数据库存在
return true;
}
/**
* 执行SQL脚本
*
* @param path SQL脚本文件的路径
* @param isClasspath SQL脚本路径是否是classpath路径
* @param connection 数据库连接对象,通过这个连接执行脚本
*/
private void runSQLScript(String path, boolean isClasspath, Connection connection) {
try (InputStream sqlFileStream = isClasspath ? new ClassPathResource(path).getStream() : new FileInputStream(path)) {
BufferedReader sqlFileStreamReader = new BufferedReader(new InputStreamReader(sqlFileStream, StandardCharsets.UTF_8));
// 创建SQL脚本执行器对象
ScriptRunner scriptRunner = new ScriptRunner(connection);
// 使用SQL脚本执行器对象执行脚本
scriptRunner.runScript(sqlFileStreamReader);
// 最后关闭文件读取器
sqlFileStreamReader.close();
} catch (Exception e) {
log.error("读取文件或者执行脚本失败!");
e.printStackTrace();
}
}
/**
* 执行SQL脚本以创建数据库
*/
private void createDatabase() {
try {
// 修改连接语句,重新建立连接
// 重新建立的连接不再连接到指定库,而是直接连接到整个MySQL
// 使用URI类解析并拆解连接地址,重新组装
URI databaseURI = new URI(url.replace("jdbc:", ""));
// 得到连接地址中的数据库平台名(例如mysql)
String databasePlatform = databaseURI.getScheme();
// 得到连接地址和端口
String hostAndPort = databaseURI.getAuthority();
// 得到连接地址中的库名
String databaseName = databaseURI.getPath().substring(1);
// 组装新的连接URL,不连接至指定库
String newURL = "jdbc:" + databasePlatform + "://" + hostAndPort + "/";
// 重新建立连接
Connection connection = DriverManager.getConnection(newURL, username, password);
Statement statement = connection.createStatement();
// 执行SQL语句创建数据库
statement.execute("create database if not exists `" + databaseName + "`");
// 关闭会话和连接
statement.close();
connection.close();
log.info("创建数据库完成!");
} catch (URISyntaxException e) {
log.error("数据库连接URL格式错误!");
throw new RuntimeException(e);
} catch (SQLException e) {
log.error("连接失败!");
throw new RuntimeException(e);
}
}
/**
* 该方法用于检测数据库是否需要初始化,如果是则执行SQL脚本进行初始化操作
*/
@PostConstruct
private void initDatabase() {
log.info("开始检查数据库是否需要初始化...");
// 检测当前连接数据库是否存在
if (currentDatabaseExists()) {
log.info("数据库存在,不需要初始化!");
return;
}
log.warn("数据库不存在!准备执行初始化步骤...");
// 先创建数据库
createDatabase();
// 然后再次连接,执行脚本初始化库中的表格
try (Connection connection = DriverManager.getConnection(url, username, password)) {
runSQLScript("/sql/init.sql", true, connection);
log.info("初始化数据库完成!");
} catch (Exception e) {
log.error("初始化数据库失败!",e.getMessage());
}
}
}
2.手动配置数据库连接池初始化Bean
//关键说明 该注释表示数据库连接池初始化依赖于databaseInitialize类初始化完毕后才可进行初始化。
@DependsOn("databaseInitialize")
@Configuration
public class DataSourceConfig {
/**
* 读取连接地址
*/
@Value("${spring.datasource.url}")
private String url;
/**
* 读取用户名
*/
@Value("${spring.datasource.username}")
private String username;
/**
* 读取密码
*/
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.hikari.connection-timeout}")
private Integer connectionTimeout;
@Value("${spring.datasource.hikari.auto-commit}")
private Boolean autoCommit;
@Value("${spring.datasource.hikari.minimum-idle}")
private Integer minimumIdle;
@Value("${spring.datasource.hikari.maximum-pool-size}")
private Integer maximumPoolSize;
@Value("${spring.datasource.hikari.idle-timeout}")
private Integer idleTimeout;
@Value("${spring.datasource.hikari.validation-timeout}")
private Long validationTimeout;
@Value("${spring.datasource.hikari.max-lifetime}")
private Long maxLifetime;
@Value("${spring.datasource.hikari.connection-test-query}")
private String testQuery;
@Bean
public DataSource createDateSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setConnectionTimeout(connectionTimeout);
config.setAutoCommit(autoCommit);
config.setConnectionTestQuery(testQuery);
config.setValidationTimeout(validationTimeout);
config.setIdleTimeout(idleTimeout);
config.setMinimumIdle(minimumIdle);
config.setMaximumPoolSize(maximumPoolSize);
config.setMaxLifetime(maxLifetime);
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
转载自:https://juejin.cn/post/7374231256115118117