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 
相关推荐
云和数据.ChenGuang5 小时前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys6 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver
Hacker_LaoYi6 小时前
SQL注入的那些面试题总结
数据库·sql
建投数据7 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
Hacker_LaoYi8 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀8 小时前
Redis梳理
数据库·redis·缓存
独行soc8 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天8 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
工业甲酰苯胺8 小时前
分布式系统架构:服务容错
数据库·架构