springboot 项目启动检查创建数据库,执行脚本,以及初始化数据源
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;
}
}
更多推荐
所有评论(0)