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 
相关推荐
李广坤4 小时前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
NE_STOP8 小时前
springMVC-HTTP消息转换器与文件上传、下载、异常处理
spring
洋洋技术笔记10 小时前
Spring Boot配置管理最佳实践
spring boot
用户8307196840821 天前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide1 天前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
爱可生开源社区1 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
玹外之音1 天前
Spring AI MCP 实战:将你的服务升级为 AI 可调用的智能工具
spring·ai编程
来一斤小鲜肉1 天前
Spring AI入门:第一个AI应用跑起来
spring·ai编程
NE_STOP1 天前
springMVC-常见视图组件与RESTFul编程风格
spring
大道至简Edward1 天前
Spring Boot 2.7 + JDK 8 升级到 Spring Boot 3.x + JDK 17 完整指南
spring boot·后端