Spring容器初始化之前,利用SpringBoot监听器做一些数据库方面的检查

之所以把数据库的连接、结构、最小初始化等检查项放到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 
相关推荐
l1t13 分钟前
DeepSeek总结的PostgreSQL因选择而宽松,因偶然而永久
数据库·postgresql
Nturmoils19 分钟前
数据同步中断后,KFS 怎么把链路接回来
数据库
她的男孩24 分钟前
企业接口照样拦得住:独立 Flyway、@RequiresFeature 与离线许可证
java·spring boot·后端
十年Java程序媛25 分钟前
Lambda 与函数式接口|别只会复制 ()->{},底层规则和坑一次性讲清
java·spring boot·后端
leisoo809739 分钟前
股票筹码分布怎么用获利比例成本区间与集中度实战 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·开源
字节跳动的猫1 小时前
LikeShop 商品评价体系二开:追评、晒图审核与评价标签筛选功能开发
运维·数据结构·数据库
m0_587383001 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
木头科技1 小时前
【AI 工程化第八篇】Spring AI 多模型路由实战:DeepSeek、通义、Kimi、OpenAI 如何统一接入和自动切换
java·人工智能·spring
马剑威(威哥爱编程)2 小时前
【AI全栈后端12-11】Spring Boot 把 AI 接口真正上线扛量:限流 / 降级 / 可观测
人工智能·spring boot·后端
行业研究员2 小时前
Agent Memory降低Token消耗原理解析
数据库·人工智能·oracle·腾讯云·智能体