Spring 引导未执行 schema.sql 脚本
Spring boot not executing schema.sql script
我正在开发 Spring 启动 Web 应用程序,如果尚未创建 MySql 数据库,我想创建它。所以我已经转储了我当前的数据库,以便拥有它的空模式。放在/src/main/resources里,maven在建war文件的时候把它带到/WEB-INF/classes里。这就是我的 application.properties 的配置方式(根据 Spring docs,应该从脚本创建数据库):
# DataSource settings: set here configurations for the database connection
spring.datasource.url = jdbc:mysql://localhost:3306/working_zones
spring.datasource.username = root
spring.datasource.password = password
spring.datasource.driverClassName = com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database = MYSQL
# Hibernate settings are prefixed with spring.jpa.hibernate.*
spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
这就是我尝试 运行 应用程序时遇到的错误(它抱怨不存在的数据库):
2015-01-13 13:30:24.334 [main] ERROR o.s.boot.SpringApplication - Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.sql.DataSource org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.dataSource; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script; nested exception is org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'working_zones'
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:301) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:302) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:370) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:302) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:975) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:752) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109) ~[spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:952) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:941) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
所以 Spring 似乎甚至尝试连接到数据库 schema.sql
,其中包含创建数据库的脚本,但未执行。 Stack Overflow 中有一些相关问题,但仍然无法正常工作,即使我尝试 spring.datasource.initialize=true
...
好吧,看起来你不能通过普通 JDBC 连接来做到这一点:creating a database in mysql from java
因此 Spring Boot 无法自动为您执行此操作。
请不要将数据库创建与其内容的创建混为一谈:表、过程、触发器等。
更新
是的,您可以在应用程序启动时执行此操作。你只是一个单独的初始化程序,它在 dataSourceInitializer
.
之前有一个订单
应用程序启动时的侦听器可以解决它。以下是代码。
public class DatabaseCreationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private AtomicBoolean received = new AtomicBoolean(false);
private ConfigurableEnvironment environment;
private Pattern JDBC_URL_PATTERN = Pattern.compile("jdbc:([a-zA-Z0-9_]+)://[0-9.:]+(?:/([a-zA-Z0-9_]+))?(\?.*)?");
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// Think about twice invoking this listener
if (!received.compareAndSet(false, true)) {
return;
}
environment = event.getEnvironment();
// ConditionalOnClass
ClassLoader classLoader = event.getSpringApplication().getClassLoader();
if (!isPresent("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType", classLoader)) {
return;
}
// DatabaseProperties
val databaseProperties = bind(DatabaseProperties.PREFIX, new DatabaseProperties());
if (!databaseProperties.shouldCreate()) {
return;
}
// DataSourceProperties
val dataSourceProperties = bind(databaseProperties.getDatasourceConfigPrefix(), new DataSourceProperties());
// Check for connection url
String url = dataSourceProperties.getUrl();
if (url == null) return;
Matcher matcher = JDBC_URL_PATTERN.matcher(url);
if (!matcher.matches()) return;
// Extract database provider and schema name from connection url
String databaseProvider = matcher.group(1);
String schemaName = matcher.group(2);
if (isBlank(schemaName)) return;
// Reset connection url
dataSourceProperties.setUrl(url.replace("/" + schemaName, ""));
// Build a new datasource and do create schema
DataSource dataSource = buildDataSource(dataSourceProperties);
try (Connection connection = DataSourceUtils.getConnection(dataSource)) {
connection.createStatement().execute(createSchemaIfAbsent(databaseProvider, schemaName));
} catch (SQLException ignored) {
}
}
private <T> T bind(String prefix, T t) {
RelaxedDataBinder binder = new RelaxedDataBinder(t, prefix);
binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
return t;
}
private static DataSource buildDataSource(DataSourceProperties dataSourceProperties) {
String url = dataSourceProperties.getUrl();
String username = dataSourceProperties.getUsername();
String password = dataSourceProperties.getPassword();
return new SingleConnectionDataSource(url, username, password, false);
}
private static String createSchemaIfAbsent(String databaseProvider, String schemaName) {
DatabaseDialects dialects = DatabaseDialects.getDatabaseDialect(databaseProvider);
if (dialects == null) {
throw new IllegalArgumentException("Unknown schema:" + schemaName);
}
switch (dialects) {
case MYSQL:
return "CREATE DATABASE IF NOT EXISTS " + schemaName;
default:
throw new UnsupportedOperationException("Unsupported schema:" + dialects);
}
}
}
下面是数据库属性。
@Data
@ConfigurationProperties(prefix = DatabaseProperties.PREFIX)
public class DatabaseProperties {
public final static String PREFIX = "spring.database";
private boolean autoCreate;
private String datasourceConfigPrefix = "spring.datasource";
public boolean shouldCreate() {
return isAutoCreate() && isNotBlank(getDatasourceConfigPrefix());
}
}
侦听器应由 META-INF/spring.factories.
中的配置激活
org.springframework.context.ApplicationListener=\
yourpackage.DatabaseCreationListener
如果您想在底层 IDE 中添加配置语法提示,请添加可选的依赖项 spring-boot-configuration-processor
和文件 META-INF/additional-spring-configuration-metadata.json.
{
"groups": [
{
"sourceType": "yourpackage.DatabaseProperties",
"name": "spring.database",
"type": "yourpackage.DatabaseProperties"
}
],
"properties": [
{
"sourceType": "yourpackage.DatabaseProperties",
"defaultValue": false,
"name": "auto-create",
"type": "java.lang.Boolean"
},
{
"sourceType": "youpackage.DatabaseProperties",
"defaultValue": "spring.datasource",
"name": "datasource-config-prefix",
"type": "java.lang.String"
}
]
}
如this github post中所述,您可以添加:
spring.datasource.url=jdbc:mysql://localhost:3309/course_api_db?createDatabaseIfNotExist=true
执行schema.sql
我正在开发 Spring 启动 Web 应用程序,如果尚未创建 MySql 数据库,我想创建它。所以我已经转储了我当前的数据库,以便拥有它的空模式。放在/src/main/resources里,maven在建war文件的时候把它带到/WEB-INF/classes里。这就是我的 application.properties 的配置方式(根据 Spring docs,应该从脚本创建数据库):
# DataSource settings: set here configurations for the database connection
spring.datasource.url = jdbc:mysql://localhost:3306/working_zones
spring.datasource.username = root
spring.datasource.password = password
spring.datasource.driverClassName = com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database = MYSQL
# Hibernate settings are prefixed with spring.jpa.hibernate.*
spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
这就是我尝试 运行 应用程序时遇到的错误(它抱怨不存在的数据库):
2015-01-13 13:30:24.334 [main] ERROR o.s.boot.SpringApplication - Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.sql.DataSource org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.dataSource; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script; nested exception is org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'working_zones'
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:301) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:302) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:370) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:302) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) ~[spring-beans-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:975) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:752) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) ~[spring-context-4.0.8.RELEASE.jar:4.0.8.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109) ~[spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:952) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:941) [spring-boot-1.1.9.RELEASE.jar:1.1.9.RELEASE]
所以 Spring 似乎甚至尝试连接到数据库 schema.sql
,其中包含创建数据库的脚本,但未执行。 Stack Overflow 中有一些相关问题,但仍然无法正常工作,即使我尝试 spring.datasource.initialize=true
...
好吧,看起来你不能通过普通 JDBC 连接来做到这一点:creating a database in mysql from java
因此 Spring Boot 无法自动为您执行此操作。
请不要将数据库创建与其内容的创建混为一谈:表、过程、触发器等。
更新
是的,您可以在应用程序启动时执行此操作。你只是一个单独的初始化程序,它在 dataSourceInitializer
.
应用程序启动时的侦听器可以解决它。以下是代码。
public class DatabaseCreationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private AtomicBoolean received = new AtomicBoolean(false);
private ConfigurableEnvironment environment;
private Pattern JDBC_URL_PATTERN = Pattern.compile("jdbc:([a-zA-Z0-9_]+)://[0-9.:]+(?:/([a-zA-Z0-9_]+))?(\?.*)?");
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// Think about twice invoking this listener
if (!received.compareAndSet(false, true)) {
return;
}
environment = event.getEnvironment();
// ConditionalOnClass
ClassLoader classLoader = event.getSpringApplication().getClassLoader();
if (!isPresent("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType", classLoader)) {
return;
}
// DatabaseProperties
val databaseProperties = bind(DatabaseProperties.PREFIX, new DatabaseProperties());
if (!databaseProperties.shouldCreate()) {
return;
}
// DataSourceProperties
val dataSourceProperties = bind(databaseProperties.getDatasourceConfigPrefix(), new DataSourceProperties());
// Check for connection url
String url = dataSourceProperties.getUrl();
if (url == null) return;
Matcher matcher = JDBC_URL_PATTERN.matcher(url);
if (!matcher.matches()) return;
// Extract database provider and schema name from connection url
String databaseProvider = matcher.group(1);
String schemaName = matcher.group(2);
if (isBlank(schemaName)) return;
// Reset connection url
dataSourceProperties.setUrl(url.replace("/" + schemaName, ""));
// Build a new datasource and do create schema
DataSource dataSource = buildDataSource(dataSourceProperties);
try (Connection connection = DataSourceUtils.getConnection(dataSource)) {
connection.createStatement().execute(createSchemaIfAbsent(databaseProvider, schemaName));
} catch (SQLException ignored) {
}
}
private <T> T bind(String prefix, T t) {
RelaxedDataBinder binder = new RelaxedDataBinder(t, prefix);
binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
return t;
}
private static DataSource buildDataSource(DataSourceProperties dataSourceProperties) {
String url = dataSourceProperties.getUrl();
String username = dataSourceProperties.getUsername();
String password = dataSourceProperties.getPassword();
return new SingleConnectionDataSource(url, username, password, false);
}
private static String createSchemaIfAbsent(String databaseProvider, String schemaName) {
DatabaseDialects dialects = DatabaseDialects.getDatabaseDialect(databaseProvider);
if (dialects == null) {
throw new IllegalArgumentException("Unknown schema:" + schemaName);
}
switch (dialects) {
case MYSQL:
return "CREATE DATABASE IF NOT EXISTS " + schemaName;
default:
throw new UnsupportedOperationException("Unsupported schema:" + dialects);
}
}
}
下面是数据库属性。
@Data
@ConfigurationProperties(prefix = DatabaseProperties.PREFIX)
public class DatabaseProperties {
public final static String PREFIX = "spring.database";
private boolean autoCreate;
private String datasourceConfigPrefix = "spring.datasource";
public boolean shouldCreate() {
return isAutoCreate() && isNotBlank(getDatasourceConfigPrefix());
}
}
侦听器应由 META-INF/spring.factories.
中的配置激活org.springframework.context.ApplicationListener=\
yourpackage.DatabaseCreationListener
如果您想在底层 IDE 中添加配置语法提示,请添加可选的依赖项 spring-boot-configuration-processor
和文件 META-INF/additional-spring-configuration-metadata.json.
{
"groups": [
{
"sourceType": "yourpackage.DatabaseProperties",
"name": "spring.database",
"type": "yourpackage.DatabaseProperties"
}
],
"properties": [
{
"sourceType": "yourpackage.DatabaseProperties",
"defaultValue": false,
"name": "auto-create",
"type": "java.lang.Boolean"
},
{
"sourceType": "youpackage.DatabaseProperties",
"defaultValue": "spring.datasource",
"name": "datasource-config-prefix",
"type": "java.lang.String"
}
]
}
如this github post中所述,您可以添加:
spring.datasource.url=jdbc:mysql://localhost:3309/course_api_db?createDatabaseIfNotExist=true
执行schema.sql