• 基于Springboot的数据库初始化工具


    前言

    极少数场景,会有数据库初始化的需求,具体表现为以下几点:
    1、项目启动时没有建库建表。
    2、每次启动时需要重置数据库。
    3、每次启动时需要更新表结构及数据。
    在这类需求下,如果有一个工具,可以在项目启动时,自动做到以上几点,就省去每次项目启动/重启前,人工进行数据库处理。

    实现方式

    基本原理

    • 利用Spring自带的Order注解,项目启动时自动执行代码。
    • 有库的情况下,利用Spring自带的DataSource连接数据库。
    • 没库的情况下,利用原生JDBC连接数据库地址,建库。
    • 利用原生JDBC执行相关建表、插入数据等操作。

    主要代码

    DbInitRunner

    • 代码执行入口类
    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(); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    DefaultActuator

    • 执行器基础实现,可通过继承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;
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    开源地址

    • 此工具已经在Github上开源,第一版(未实现SpringBootStarter)已经上传至Maven中央仓库。
    • GitHub地址:【db-init
    • Gitee地址:【db-init
  • 相关阅读:
    Maven最新版的下载与安装教程(详细教程)
    Visual studio代码提示(IntelliSense)的语言(包括汉化等)修改
    xml的语法
    事件对象学习
    基于JAVA的俄罗斯方块游戏的设计与实现
    【XInput】手柄模拟鼠标运作之 .NET P/Invoke 和 UWP-API 方案
    Qt Creator编译含opencv的程序时报错libopencv_calib3d.so:-1: error: error adding symbols
    【C#】vs2022 .net8
    Springboot+vue的班级综合测评管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。
    12个MySQL慢查询的原因分析
  • 原文地址:https://blog.csdn.net/hu18315778112/article/details/126010628