springboot 项目启动检查创建数据库,执行脚本,以及初始化数据源

背景: 因为项目需要部署到项目现场,新的电脑环境虽然安装了MySQL,但是部署人员不一定会执行数据库脚本。每次通过Navicat等软件也比较麻烦。所以需要在项目启动前检查是否存在数据库,如果不存在就创建数据库并且执行数据库脚本。如果存在就跳过。

项目配置文件application.yml

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
    username: XXX
    password: XXX
    driver-class-name: com.mysql.cj.jdbc.Driver
    platform: mysql
    schema: classpath:/sql/schema-mysql.sql
    #data: classpath:/sql/data-mysql.sql
    initialization-mode: always
    continue-on-error: true
    sql-script-encoding: utf-8
    separator: ;
    main:
      url: jdbc:mysql://127.0.0.1:3306/XXX?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
      username: XXX
      password: XXX
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      continue-on-error: true
      initial-size: 5
      min-idle: 5
      max-active: 200
      max-wait: 60000
      break-after-acquire-failure: false
      connection-error-retry-attempts: 3
      filters: stat,wall,slf4j

检查数据库执行类(检查是否存在指定的数据库–不存在则创建数据库-否则跳过)

package xxx.xxx.xxx;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.sql.*;

@Configuration
public class DatabaseInitConfig {
    private static final Logger LOG = LoggerFactory.getLogger(DatabaseInitConfig.class);

    private static final String SCHEMA_NAME = "schema_name";

    private static final String DATABASE_NAME = "xxx";

    /**
     * com.mysql.cj.jdbc.Driver
     */
    @Value("${spring.datasource.driver-class-name}")
    private String driver;
    /**
     * jdbc_url
     */
    @Value("${spring.datasource.url}")
    private String url;
    /**
     * 账号名称
     */
    @Value("${spring.datasource.username}")
    private String username;
    /**
     * 账号密码
     */
    @Value("${spring.datasource.password}")
    private String password;
    /**
     * 需要创建的数据名称
     */

    @PostConstruct
    public void init() {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException  e) {
            LOG.error("JDBC URL解析错误", e);
        }

        try (
            Connection connection = DriverManager.getConnection(url, username, password);
            Statement statement = connection.createStatement()) {
            String sal = "select schema_name from information_schema.schemata where schema_name = " + "'" + DATABASE_NAME + "'";

            //查询返回的结果集
            ResultSet resultSet = statement.executeQuery(sal);
            if (!resultSet.next()) {
                //查不到数据库,执行数据库初始化脚本
                LOG.warn("查不到数据库({})", DATABASE_NAME);
                String createDb = "CREATE DATABASE IF NOT EXISTS " + DATABASE_NAME
                        + " DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci ";
                connection.setAutoCommit(false);
                statement.execute(createDb);
                connection.commit();
                LOG.info("创建数据库({})成功", DATABASE_NAME);
            } else {
                String databaseName = resultSet.getString(SCHEMA_NAME);
                LOG.warn("已经存在数据库({})", databaseName);
            }
            if (resultSet.isClosed()) {
                resultSet.close();
            }
        } catch (SQLException e) {
            LOG.error("启动项目检查数据库是否创建报错", e);
        }

    }

}

@Configuration 知识点
用于定义配置类,可替换XML配置文件,被注解的类内部包含一个或多个@Bean注解方法。可以被AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext 进行扫描。用于构建bean定义以及初始化Spring容器。

@PostConstruct说明
被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。

@PreConstruct说明
被@PreConstruct修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreConstruct修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前

执行脚本可以通过yml配置实现

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
    username: XXX
    password: XXX
    driver-class-name: com.mysql.cj.jdbc.Driver
    platform: mysql
    schema: classpath:/sql/schema-mysql.sql
    data: classpath:/sql/data-mysql.sql
    initialization-mode: always

配置数据源类

控制数据源初始化在检查数据库完成之后

@DependsOn 控制bean初始化顺序
可能有些场景中,bean A 间接依赖 bean B。如Bean B应该需要更新一些全局缓存,可能通过单例模式实现且没有在spring容器注册,bean A需要使用该缓存;因此,如果bean B没有准备好,bean A无法访问。
另一个场景中,bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。
简言之,有很多场景需要bean B应该被先于bean A被初始化,从而避免各种负面影响。我们可以在bean A上使用@DependsOn注解,告诉容器bean B应该先被初始化。下面通过示例来说明。

package xxx.xxx;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
public class DataSourceConfig {
    private static final Logger LOG = LoggerFactory.getLogger(DataSourceConfig.class);

    /**
     * spring监控,druid的拦截器
     *
     * @return
     */
    @Bean
    public DruidStatInterceptor druidStatInterceptor() {
        return new DruidStatInterceptor();
    }

    @Bean
    @Scope("prototype")
    public JdkRegexpMethodPointcut druidStatPointcut() {
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        pointcut.setPatterns("com.xx.xxxx.*.service.*.service.*", "com.xxxx.xxxx.*.service.*.mapper.*");
        return pointcut;
    }

    /**
     * aop配置
     *
     * @param druidStatInterceptor
     * @param druidStatPointcut
     * @return
     */
    @Bean
    public DefaultPointcutAdvisor druidStatAdvisor(DruidStatInterceptor druidStatInterceptor, JdkRegexpMethodPointcut druidStatPointcut) {
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(druidStatInterceptor);
        advisor.setPointcut(druidStatPointcut);
        return advisor;
    }

    private DruidDataSource dataSource;

    @Bean
    @DependsOn("databaseInitConfig")
    public DataSource init(
            @Value("${spring.datasource.main.driver-class-name}")
                    String driverClassName,
            @Value("${spring.datasource.main.username}")
                    String username,
            @Value("${spring.datasource.main.password}")
                    String password,
            @Value("${spring.datasource.main.url}")
                    String url,
            @Value("${spring.datasource.main.initial-size}")
                    Integer initialSize,
            @Value("${spring.datasource.main.min-idle}")
                    Integer minIdle,
            @Value("${spring.datasource.main.max-active}")
                    Integer maxActive,
            @Value("${spring.datasource.main.max-wait}")
                    Integer maxWait,
            @Value("${spring.datasource.main.filters}")
                    String filters
    ) {
        try {

            if (dataSource != null) {
                return dataSource;
            }
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setDriverClassName(driverClassName);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setUrl(url);
            druidDataSource.setInitialSize(initialSize);
            druidDataSource.setMinIdle(minIdle);
            druidDataSource.setMaxActive(maxActive);
            druidDataSource.setMaxWait(maxWait);
            druidDataSource.setFilters(filters);
            return druidDataSource;
        } catch (SQLException e) {
            LOG.error("初始化数据源出错", e);
        }
        return null;
    }
}

猜你喜欢

转载自blog.csdn.net/zzchances/article/details/127687645