背景: 因为项目需要部署到项目现场,新的电脑环境虽然安装了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;
- }
- }