[bs-94] Add database initializer if schema.sql detected

* If an embedded database is created it is also initialized from
classpath:/schema.sql if it exists
* Also added (but didn't use) @ConditionalOnResource

[Fixes #49142305]
This commit is contained in:
Dave Syer 2013-05-02 18:07:53 +01:00
parent a6fd8cad76
commit 47f60684e3
5 changed files with 220 additions and 15 deletions

View File

@ -21,6 +21,25 @@
<module>spring-bootstrap-jpa-application</module>
<module>spring-bootstrap-web-application</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap</artifactId>
<version>${spring.bootstrap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-service</artifactId>
<version>${spring.bootstrap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-web-application</artifactId>
<version>${spring.bootstrap.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>

View File

@ -23,7 +23,7 @@ production, and in other environments.
|Database |HSQLDB or H2 | Per classpath, or define a DataSource to override |
|Externalized configuration | Properties or YAML | Support for Spring profiles. Bind automatically to @Bean. |
|Audit | Spring Security and Spring ApplicationEvent |Flexible abstraction with sensible defaults for security events |
|Validation | JSR-303 | |
|Validation | JSR-303 |If on the classpath |
|Management endpoints | Spring MVC | Health, basic metrics, request tracing, shutdown, thread dumps |
|Error pages | Spring MVC | Sensible defaults based on exception and status code |
|JSON |Jackson 2 | |
@ -35,8 +35,11 @@ production, and in other environments.
You will need Java (6 at least) and a build tool (Maven is what we use
below, but you are more than welcome to use gradle). These can be
downloaded or installed easily in most operating systems. FIXME:
short instructions for Mac and Linux.
downloaded or installed easily in most operating systems. For Ubuntu:
$ sudo apt-get install openjdk-6-jdk maven
<!--FIXME: short instructions for Mac.-->
## A basic project
@ -53,19 +56,14 @@ If you are using Maven create a really simple `pom.xml` with 2 dependencies:
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<spring.bootstrap.version>0.0.1-SNAPSHOT</spring.bootstrap.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-web-application</artifactId>
<version>${spring.bootstrap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-service</artifactId>
<version>${spring.bootstrap.version}</version>
</dependency>
</dependencies>
<build>
@ -82,8 +80,8 @@ If you like Gradle, that's fine, and you will know what to do with
those dependencies. The first dependency adds Spring Bootstrap auto
configuration and the Jetty container to your application, and the
second one adds some more opinionated stuff like the default
management endpoints. If you prefer Tomcat FIXME: use a different
dependency.
management endpoints. If you prefer Tomcat you can just add the
embedded Tomcat jars to your classpath instead of Jetty.
You should be able to run it already:
@ -143,7 +141,6 @@ use the main method to launch it from your project jar. Just add a
the fully qualified name of your `SampleController`, e.g.
<properties>
<spring.bootstrap.version>0.0.1-SNAPSHOT</spring.bootstrap.version>
<start-class>com.mycompany.sample.SampleController</start-class>
</properties>
@ -171,7 +168,7 @@ on a class and run it.
4. Find feature in Gradle that does the same thing.
5. Use the Spring executable. FIXME: document this maybe.
5. Use the Spring executable. <!--FIXME: document this maybe.-->
## Externalizing configuration
@ -301,8 +298,9 @@ In the `pom.xml` it would look like this:
</dependency>
(Spring Security java config is still work in progress so we have used
a snapshot. Beware of sudden changes. FIXME: update to full
release.)
a snapshot. Beware of sudden changes.)
<!--FIXME: update Spring Security to full release -->
Try it out:
@ -333,4 +331,57 @@ Try it out:
Just add `spring-jdbc` and an embedded database to your dependencies:
FIXME: TBD
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
Then you will be able to inject a `DataSource` into your controller:
@Controller
@EnableAutoConfiguration
@EnableConfigurationProperties(ServiceProperties.class)
public class SampleController {
private JdbcTemplate jdbcTemplate;
@Autowired
public SampleController(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@RequestMapping("/")
@ResponseBody
public Map<String, String> helloWorld() {
return jdbcTemplate.queryForMap("SELECT * FROM MESSAGES WHERE ID=?", 0);
}
...
}
The app will run (going back to the default security configuration):
$ curl user:password@localhost:8080/
{"error":"Internal Server Error", "status":500, "exception":...}
but there's no data in the database yet and the `MESSAGES` table
doesn't even exist, so there's an error. One easy way to fix it is
to provide a `schema.sql` script in the root of the classpath, e.g.
create table MESSAGES (
ID BIGINT NOT NULL PRIMARY KEY,
MESSAGE VARCHAR(255)
);
INSERT INTO MESSAGES (ID, MESSAGE) VALUES (0, 'Hello Phil');
Now when you run the app you get a sensible response:
$ curl user:password@localhost:8080/
{"ID":0, "MESSAGE":"Hello Phil"}
Obviously, this is only the start, but hopefully you have a good grasp
of the basics and are ready to try it out yourself.

View File

@ -20,8 +20,11 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
@ -29,10 +32,15 @@ import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.task.AsyncUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.ClassUtils;
/**
@ -54,6 +62,23 @@ public class EmbeddedDatabaseAutoConfiguration {
private ExecutorService executorService;
@Autowired
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@Value("${spring.jdbc.schema:classpath:schema.sql}")
private String schemaLocation = "classpath:schema.sql"; // FIXME: DB platform
@PostConstruct
protected void initialize() throws Exception {
Resource resource = this.resourceLoader.getResource(this.schemaLocation);
if (resource.exists()) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(resource);
populator.setContinueOnError(true);
DatabasePopulatorUtils.execute(populator, dataSource());
}
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that only matches when the specified resources are on the
* classpath.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
/**
* The resources that must be present.
* @return the resource paths that must be present.
*/
public String[] resources() default {};
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
/**
* {@link Condition} that checks for specific resources.
*
* @author Dave Syer
* @see ConditionalOnResource
*/
class OnResourceCondition implements Condition {
private ResourceLoader loader = new DefaultResourceLoader();
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnClass.class.getName(), true);
if (attributes != null) {
List<String> locations = new ArrayList<String>();
collectValues(locations, attributes.get("resources"));
Assert.isTrue(locations.size() > 0,
"@ConditionalOnResource annotations must specify at least one resource location");
for (String location : locations) {
if (!this.loader.getResource(location).exists()) {
return false;
}
}
}
return true;
}
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {
for (Object item : (Object[]) value) {
names.add((String) item);
}
}
}
}