之所以把数据库的连接、结构、最小初始化等检查项放到SpringBoot监听器而不是Spring容器的初始过程。是有原因的。
1:SpringBoot监听器是SpringBoot初始化过程中,最先被执行的那一批周期函数。
2:数据库连接测试能很快的获得结果。
3:Spring容器初始化时,里面的ApplicationListener,ApplicationRunner,初始化方法等有部分实现是异步预热缓存数据。如果不前置检查数据表结构,很容易报sql异常。当产品有更新表结构时,项目发布时,很容易出现该问题。
java
public class PrepareTableCheckListener implements SpringApplicationRunListener {
private static List<String> MYSQL_PRODUCT_NAMES = new ArrayList<>();
static {
MYSQL_PRODUCT_NAMES.add("kingbase8");
MYSQL_PRODUCT_NAMES.add("mysql");
}
public PrepareTableCheckApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
String jdbcDriver = environment.getProperty("spring.datasource.driver-class-name");
String jdbcUrl = environment.getProperty("spring.datasource.url");
String jdbcUserName = environment.getProperty("spring.datasource.username");
String jdbcUserPassword = environment.getProperty("spring.datasource.password");
String testNetworkSQL = environment.getProperty("spring.datasource.hikari.connection-test-query");
if (null == jdbcUrl || null == jdbcUserPassword || null == jdbcUserName) {
return;
}
loadDriverClass(jdbcDriver);
// 数据库连接检查
DriverManager.setLoginTimeout(2);
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcUserPassword)
) {
Statement statement = conn.createStatement();
statement.execute(testNetworkSQL);
} catch (Exception e) {
log.warn("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
log.warn("...................★");
log.warn("..................▍..★");
log.warn("..................▍.一 .☆");
log.warn("................. ▍ ..帆. ★");
log.warn("..................▍ ... 风. ☆");
log.warn("..................▍ ... ..顺. ★");
log.warn("................. ▍.万 事 如 意. ☆");
log.warn("..................▍☆ .★ .☆ .★. ☆");
log.warn("..................▍");
log.warn("..▍∵ ☆ ★...▍▍....█▍ ☆ ★∵▍..");
log.warn("◥█▅▅██▅▅██▅▅▅▅▅███◤");
log.warn(".◥███████████████◤");
log.warn("~~~~◥█████████████◤~~~~");
log.warn("~~~~~~~~~~~~~~~~~~~~~~~~~");
log.warn("~~~哦豁,连不上数据库,哈哈哈哈哈~~~~~~~");
log.warn("~~~~~~~~~~~~~~~~~~~~~~~~~");
log.warn("+jdbcDriver={}", jdbcDriver);
log.warn("+jdbcUrl={}", jdbcUrl);
log.warn("+jdbcUserName={}", jdbcUserName);
log.warn("+jdbcUserPassword={}", jdbcUserPassword);
log.warn("+testNetworkSQL={}", testNetworkSQL);
log.warn("+exception class =" + e.getClass().getName());
log.warn("+exception message =" + e.getMessage());
log.warn("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
System.exit(0);
}
// 预定义表结构检查
Set<String> prepareSqls;
String urlLower = jdbcUrl.toLowerCase();
if (ZYListUtils.anyMatch(MYSQL_PRODUCT_NAMES, urlLower::contains)) {
prepareSqls = readSql("db_prepare_mysql");
} else {
prepareSqls = readSql("db_prepare_oracle");
}
if (prepareSqls.isEmpty()) {
return;
}
try (
Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUserName, jdbcUserPassword)
) {
conn.setAutoCommit(true);
Statement stmt = conn.createStatement();
for (String prepareSql : prepareSqls) {
try {
stmt.executeUpdate(prepareSql);
} catch (Exception e) {
log.warn("execute table check sql 【{}】 false ,the column or table may exit!", prepareSql);
}
}
} catch (Exception e) {
log.warn("execute table check sql false ,the Connection get false!");
}
}
public Set<String> readSql(String dir) {
Set<String> sqls = new HashSet<>();
Resource[] resources;
try {
resources = new PathMatchingResourcePatternResolver().getResources("classpath*:/" + dir + "/**/*.sql");
} catch (IOException e) {
return sqls;
}
if (resources.length == 0) {
return sqls;
}
for (Resource resource : resources) {
try (InputStream inputStream = resource.getInputStream()) {
String sqlText = IoUtil.read(inputStream, Charset.defaultCharset());
String[] sqlArr = sqlText.split(";");
for (String sql : sqlArr) {
List<String> sqlItems = new ArrayList<>();
StringTokenizer stringTokenizer = new StringTokenizer(sql);
while (stringTokenizer.hasMoreTokens()) {
String item = stringTokenizer.nextToken();
sqlItems.add(item);
}
String finalSql = ZYStrUtils.join(sqlItems, " ");
sqls.add(finalSql);
}
} catch (Exception e) {
log.warn("sql read false");
}
}
return sqls;
}
private void loadDriverClass(String jdbcDriver) {
try {
Class.forName(jdbcDriver);
} catch (ClassNotFoundException ex) {
throw new LocalException("数据库驱动文件不存在,请检查是否缺少驱动包或spring.datasource.driver-class-name配置不正确!");
}
}
}
spring.factories配置
java
org.springframework.boot.SpringApplicationRunListener =xxx.framework.mybatis.PrepareTableCheckListener