极少数场景,会有数据库初始化的需求,具体表现为以下几点:
1、项目启动时没有建库建表。
2、每次启动时需要重置数据库。
3、每次启动时需要更新表结构及数据。
在这类需求下,如果有一个工具,可以在项目启动时,自动做到以上几点,就省去每次项目启动/重启前,人工进行数据库处理。
package io.github.dingdangdog.dbinit.runner;
import io.github.dingdangdog.dbinit.clear.AutoClear;
import io.github.dingdangdog.dbinit.config.DbBase;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import io.github.dingdangdog.dbinit.actuator.factory.DbActuatorFactory;
import io.github.dingdangdog.dbinit.config.DbInitConfig;
import io.github.dingdangdog.dbinit.log.entity.DbInitContext;
import io.github.dingdangdog.dbinit.log.manager.DbInitContextManager;
import javax.sql.DataSource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 数据库初始化入口
*
* @author DingDangDog
* @since 2022/5/10 16:36
*/
@Order(BigDecimal.ROUND_HALF_EVEN)
@Slf4j
public class DbInitRunner implements ApplicationRunner {
private final ApplicationContext context;
private final AutoClear autoClear;
private final DbInitConfig dbInitConfig;
public DbInitRunner(ApplicationContext context, AutoClear autoClear, DbInitConfig dbInitConfig) {
this.context = context;
this.autoClear = autoClear;
this.dbInitConfig = dbInitConfig;
}
@Override
public void run(ApplicationArguments args) {
log.info("--------DDD----DbInit Runner Begin----DDD--------");
List<DbBase> dbList = dbInitConfig.getDbList();
// 校验配置可用性
if (!checkConfig(dbList)) {
autoClear.clearAllByName(AutoClear.beanNameList);
return;
}
Map<String, DbBase> nameForDb = dbList.stream()
.filter(dbBase -> StringUtils.isNotEmpty(dbBase.getName()))
.collect(Collectors.toMap(DbBase::getName, Function.identity()));
log.info("--------DDD---- DbInit Datasource: {} ----DDD--------", nameForDb.keySet());
// 获取spring容器中全部数据源
Map<String, DataSource> dataSourceMap = context.getBeansOfType(DataSource.class);
DbActuatorFactory factory = new DbActuatorFactory();
dataSourceMap.forEach((key, dataSource) -> {
DbBase dbBase = nameForDb.get(key);
// 判断是否确认开启数据库初始化
if (dbBase.getEnable()) {
// 开启上下文
DbInitContext dbInitContext = DbInitContextManager.begin(key);
log.info("--------DDD---- DbInit {} Begin ----DDD--------", key);
factory.createActuator(key, dataSource, dbBase).init();
// 关闭上下文
DbInitContextManager.end();
// 输出日志
log.info("--------DDD---- DbInit {} Info: {} ----DDD--------", key, dbInitContext.toString());
log.info("--------DDD---- DbInit {} End ----DDD--------", key);
}
});
autoClear.clearAllByName(AutoClear.beanNameList);
}
/**
* 校验配置可用性
*
* @return boolean 配置可用性
*
* - true:可用
* - false:不可用
*
* @author DDD
* @date 2022/5/13 15:28
*/
private boolean checkConfig(List<DbBase> dbBaseList) {
if (CollectionUtils.isEmpty(dbBaseList)) {
log.error("--------DDD---- Undefined Datasource Config, DbInit End! ----DDD--------");
return false;
}
AtomicBoolean right = new AtomicBoolean(true);
dbBaseList.forEach(dbBase -> {
String name = dbBase.getName();
String type = dbBase.getType();
if (StringUtils.isEmpty(type)) {
log.error("--------DDD---- Datasource {} Missing Config: type, DbInit Will Stop! ----DDD--------", name);
right.set(false);
} else if (!DbInitConfig.supportType.contains(type.toUpperCase())) {
log.error("--------DDD---- Datasource {} type: {} Not Supported, DbInit Will Stop! ----DDD--------", name, type);
right.set(false);
}
// 校验创建数据库所需参数
if (dbBase.getCreate()) {
List<String> missingConfig = new ArrayList<>();
if (StringUtils.isEmpty(dbBase.getUrl())) {
missingConfig.add("url");
}
if (StringUtils.isEmpty(dbBase.getBaseName())) {
missingConfig.add("baseName");
}
if (StringUtils.isEmpty(dbBase.getUsername())) {
missingConfig.add("username");
}
if (StringUtils.isEmpty(dbBase.getPassword())) {
missingConfig.add("password");
}
if (!CollectionUtils.isEmpty(missingConfig)) {
log.error("--------DDD---- Datasource {} Missing Config {} ----DDD--------", name, missingConfig);
right.set(false);
}
}
});
return right.get();
}
}
DefaultActuator 重写不同数据源执行逻辑,如:Mysql、Oracle等package io.github.dingdangdog.dbinit.actuator;
import io.github.dingdangdog.dbinit.config.DbBase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import io.github.dingdangdog.dbinit.log.entity.DbInitContext;
import io.github.dingdangdog.dbinit.log.manager.DbInitContextManager;
import io.github.dingdangdog.file.FileUtilOm;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
* 数据库初始化配置
*
* @author DDD
* @since `2022/5/9` 13:33
*/
@Slf4j
public class DefaultActuator implements DbActuatorInterface {
protected final String name;
protected final DataSource dataSource;
protected final DbBase dbBase;
public DefaultActuator(@NonNull String name, @NonNull DataSource dataSource, DbBase dbBase) {
this.name = name;
this.dataSource = dataSource;
this.dbBase = dbBase;
}
@Override
public void init() {
if (dbBase.getCreate()) {
// 创建数据库
if (!createDataBase()) {
log.error("--------DDD---- Datasource {} Init Error: DataBase Create Exception ----DDD--------", name);
return;
}
}
// 创建 数据库连接 和 sql执行器
try (Connection conn = dataSource.getConnection();
Statement statement = conn.createStatement()) {
// 关闭自动提交
conn.setAutoCommit(false);
// 执行覆盖sql脚本
coverBySqlFile(conn, statement);
} catch (IOException e) {
log.error("--------DDD---- Datasource {} Init Connection Exception: {} ----DDD--------", name, e.getMessage());
e.printStackTrace();
} catch (SQLException e) {
log.error("--------DDD---- Datasource {} Init SQL Exception: {} ----DDD--------", name, e.getMessage());
e.printStackTrace();
}
}
@Override
public boolean createDataBase() {
return false;
}
@Override
public void coverBySqlFile(Connection conn, Statement statement) throws IOException, SQLException {
File[] files = FileUtilOm.getRootFiles(name);
log.info("--------DDD---- Datasource {} Cover Begin ----DDD--------", name);
DbInitContext context = DbInitContextManager.getContext();
context.setFileName(name);
String fileName = null;
try {
for (File file : files) {
fileName = file.getName();
if (fileName.endsWith(".sql")) {
List<String> sqlList = getEffectiveSql(file);
context.setSqlQuantity(sqlList.size());
executeSql(sqlList, statement);
}
}
} catch (SQLException | IOException e) {
log.error("--------DDD---- Datasource {} Cover Error: File {}, Message: {} ----DDD--------", name, fileName, e.getMessage());
conn.rollback();
throw e;
}
conn.commit();
log.info("--------DDD---- Datasource {} Cover Success ----DDD--------", name);
}
@Override
public List<String> getEffectiveSql(File file) throws IOException {
List<String> sqlList = new ArrayList<>();
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
String line;
boolean inNotes = false;
StringBuilder sql = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("/*")) {
inNotes = true;
}
if (inNotes || line.startsWith("-- ")) {
if (line.contains("*/")) {
inNotes = false;
}
continue;
}
sql.append(line);
sql.append(" ");
if (line.contains(";")) {
sqlList.add(sql.toString());
sql.setLength(0);
}
}
}
return sqlList;
}
@Override
public void executeSql(List<String> sqlList, Statement statement) throws SQLException {
for (String sql : sqlList) {
try {
statement.execute(sql);
} catch (SQLException e) {
log.error("--------DDD---- Datasource {} SQL Execute Error: Sql: {} Exception: {} ----DDD--------", name, sql, e.getMessage());
throw e;
}
}
}
}