likes
comments
collection
share

SpringBoot+Mybatis项目支持自动初始化数据库

作者站长头像
站长
· 阅读数 28

引言


需要用到该技术点背景: 本身笔者从事物联网相关开发,项目需要频繁在多个环境下部署。手动导入数据库脚本过于繁琐,本身Mybatis的ORM属性不够完整,故做此调研以减少重复工作量。本文参考了其他文章,主要是发现需要控制实例初始化顺序的关系。

  1. 本身Mybatis是一个数据库操作工具集定义为半ORM,不属于全ORM。所以不能支持自动初始化数据库,如果使用JPA+Hibernate是可以原生支持自动生成数据库的。
  2. 解决自动初始化数据库,需要预先准备好待初始化的SQL脚本。本质原理是在Spring容器初始化完毕前,使用数据库驱动连接数据库并检查数据库是否存在,不存在则执行预先准备好的SQL脚本进行数据库初始化。
  3. 要解决的问题
    • 创建数据库初始化类
    • 保证数据库初始化类的执行顺序是在数据库连接池初始化之前
    • 手动配置数据库连接池的Bean(用于控制初始化顺序)

正文

实现步骤: 1.创建数据库初始化类 注意“/sql/init.sql” 这个路径是指向Resource目录的相对路径。所以你的初始化SQL需要放在Resource目录,便于一起打包到JAR中。

@Slf4j
@Configuration
public class DatabaseInitialize {

    /**
     * 读取连接地址
     */
    @Value("${spring.datasource.url}")
    private String url;

    /**
     * 读取用户名
     */
    @Value("${spring.datasource.username}")
    private String username;

    /**
     * 读取密码
     */
    @Value("${spring.datasource.password}")
    private String password;

    /**
     * 检测当前连接的库是否存在(连接URL中的数据库)
     *
     * @return 当前连接的库是否存在
     */
    private boolean currentDatabaseExists() {
        // 尝试以配置文件中的URL建立连接
        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            connection.close();
        } catch (SQLException e) {
            // 若连接抛出异常则说明连接URL中指定数据库不存在
            return false;
        }
        // 正常情况下说明连接URL中数据库存在
        return true;
    }

    /**
     * 执行SQL脚本
     *
     * @param path        SQL脚本文件的路径
     * @param isClasspath SQL脚本路径是否是classpath路径
     * @param connection  数据库连接对象,通过这个连接执行脚本
     */
    private void runSQLScript(String path, boolean isClasspath, Connection connection) {
        try (InputStream sqlFileStream = isClasspath ? new ClassPathResource(path).getStream() : new FileInputStream(path)) {
            BufferedReader sqlFileStreamReader = new BufferedReader(new InputStreamReader(sqlFileStream, StandardCharsets.UTF_8));
            // 创建SQL脚本执行器对象
            ScriptRunner scriptRunner = new ScriptRunner(connection);
            // 使用SQL脚本执行器对象执行脚本
            scriptRunner.runScript(sqlFileStreamReader);
            // 最后关闭文件读取器
            sqlFileStreamReader.close();
        } catch (Exception e) {
            log.error("读取文件或者执行脚本失败!");
            e.printStackTrace();
        }
    }

    /**
     * 执行SQL脚本以创建数据库
     */
    private void createDatabase() {
        try {
            // 修改连接语句,重新建立连接
            // 重新建立的连接不再连接到指定库,而是直接连接到整个MySQL
            // 使用URI类解析并拆解连接地址,重新组装
            URI databaseURI = new URI(url.replace("jdbc:", ""));
            // 得到连接地址中的数据库平台名(例如mysql)
            String databasePlatform = databaseURI.getScheme();
            // 得到连接地址和端口
            String hostAndPort = databaseURI.getAuthority();
            // 得到连接地址中的库名
            String databaseName = databaseURI.getPath().substring(1);
            // 组装新的连接URL,不连接至指定库
            String newURL = "jdbc:" + databasePlatform + "://" + hostAndPort + "/";
            // 重新建立连接
            Connection connection = DriverManager.getConnection(newURL, username, password);
            Statement statement = connection.createStatement();
            // 执行SQL语句创建数据库
            statement.execute("create database if not exists `" + databaseName + "`");
            // 关闭会话和连接
            statement.close();
            connection.close();
            log.info("创建数据库完成!");
        } catch (URISyntaxException e) {
            log.error("数据库连接URL格式错误!");
            throw new RuntimeException(e);
        } catch (SQLException e) {
            log.error("连接失败!");
            throw new RuntimeException(e);
        }
    }

    /**
     * 该方法用于检测数据库是否需要初始化,如果是则执行SQL脚本进行初始化操作
     */
    @PostConstruct
    private void initDatabase() {
        log.info("开始检查数据库是否需要初始化...");
        // 检测当前连接数据库是否存在
        if (currentDatabaseExists()) {
            log.info("数据库存在,不需要初始化!");
            return;
        }
        log.warn("数据库不存在!准备执行初始化步骤...");
        // 先创建数据库
        createDatabase();
        // 然后再次连接,执行脚本初始化库中的表格
        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            runSQLScript("/sql/init.sql", true, connection);
            log.info("初始化数据库完成!");
        } catch (Exception e) {
            log.error("初始化数据库失败!",e.getMessage());
        }
    }

}

2.手动配置数据库连接池初始化Bean

//关键说明 该注释表示数据库连接池初始化依赖于databaseInitialize类初始化完毕后才可进行初始化。
@DependsOn("databaseInitialize")
@Configuration
public class DataSourceConfig {

    /**
     * 读取连接地址
     */
    @Value("${spring.datasource.url}")
    private String url;

    /**
     * 读取用户名
     */
    @Value("${spring.datasource.username}")
    private String username;

    /**
     * 读取密码
     */
    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.hikari.connection-timeout}")
    private Integer connectionTimeout;

    @Value("${spring.datasource.hikari.auto-commit}")
    private Boolean autoCommit;

    @Value("${spring.datasource.hikari.minimum-idle}")
    private Integer minimumIdle;

    @Value("${spring.datasource.hikari.maximum-pool-size}")
    private Integer maximumPoolSize;

    @Value("${spring.datasource.hikari.idle-timeout}")
    private Integer idleTimeout;

    @Value("${spring.datasource.hikari.validation-timeout}")
    private Long validationTimeout;

    @Value("${spring.datasource.hikari.max-lifetime}")
    private Long maxLifetime;

    @Value("${spring.datasource.hikari.connection-test-query}")
    private String testQuery;

    @Bean
    public DataSource createDateSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        config.setConnectionTimeout(connectionTimeout);
        config.setAutoCommit(autoCommit);
        config.setConnectionTestQuery(testQuery);
        config.setValidationTimeout(validationTimeout);
        config.setIdleTimeout(idleTimeout);
        config.setMinimumIdle(minimumIdle);
        config.setMaximumPoolSize(maximumPoolSize);
        config.setMaxLifetime(maxLifetime);

        HikariDataSource dataSource = new HikariDataSource(config);
        return dataSource;
    }
转载自:https://juejin.cn/post/7374231256115118117
评论
请登录