diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java index 4d0b3ccd66..f9aa4fc572 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java @@ -16,17 +16,28 @@ package org.springframework.jdbc.datasource.embedded; +import javax.sql.DataSource; + import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.util.Assert; /** * A builder that provides a convenient API for constructing an embedded database. * *

Usage Example

*
- * EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
- * EmbeddedDatabase db = builder.setType(H2).addScript("schema.sql").addScript("data.sql").build();
+ * EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
+ *     .setType(H2)
+ *     .setScriptEncoding("UTF-8")
+ *     .ignoreFailedDrops(true)
+ *     .addScript("schema.sql")
+ *     .addScripts("user_data.sql", "country_data.sql")
+ *     .build();
+ *
+ * // ...
+ *
  * db.shutdown();
  * 
* @@ -35,6 +46,9 @@ import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; * @author Dave Syer * @author Sam Brannen * @since 3.0 + * @see org.springframework.jdbc.datasource.init.ScriptUtils + * @see org.springframework.jdbc.datasource.init.ResourceDatabasePopulator + * @see org.springframework.jdbc.datasource.init.DatabasePopulatorUtils */ public class EmbeddedDatabaseBuilder { @@ -46,15 +60,15 @@ public class EmbeddedDatabaseBuilder { /** - * Create a new embedded database builder. + * Create a new embedded database builder with a {@link DefaultResourceLoader}. */ public EmbeddedDatabaseBuilder() { this(new DefaultResourceLoader()); } /** - * Create a new embedded database builder with the given ResourceLoader. - * @param resourceLoader the ResourceLoader to delegate to + * Create a new embedded database builder with the given {@link ResourceLoader}. + * @param resourceLoader the {@code ResourceLoader} to delegate to */ public EmbeddedDatabaseBuilder(ResourceLoader resourceLoader) { this.databaseFactory = new EmbeddedDatabaseFactory(); @@ -63,12 +77,11 @@ public class EmbeddedDatabaseBuilder { this.resourceLoader = resourceLoader; } - /** * Set the name of the embedded database. *

Defaults to {@link EmbeddedDatabaseFactory#DEFAULT_DATABASE_NAME} if * not called. - * @param databaseName the database name + * @param databaseName the name of the embedded database to build * @return {@code this}, to facilitate method chaining */ public EmbeddedDatabaseBuilder setName(String databaseName) { @@ -79,7 +92,7 @@ public class EmbeddedDatabaseBuilder { /** * Set the type of embedded database. *

Defaults to HSQL if not called. - * @param databaseType the database type + * @param databaseType the type of embedded database to build * @return {@code this}, to facilitate method chaining */ public EmbeddedDatabaseBuilder setType(EmbeddedDatabaseType databaseType) { @@ -88,24 +101,142 @@ public class EmbeddedDatabaseBuilder { } /** - * Add an SQL script to execute to initialize or populate the database. - * @param sqlResource the sql resource location + * Set the factory to use to create the {@link DataSource} instance that + * connects to the embedded database. + *

Defaults to {@link SimpleDriverDataSourceFactory} but can be overridden, + * for example to introduce connection pooling. * @return {@code this}, to facilitate method chaining + * @since 4.0.3 */ - public EmbeddedDatabaseBuilder addScript(String sqlResource) { - this.databasePopulator.addScript(this.resourceLoader.getResource(sqlResource)); + public EmbeddedDatabaseBuilder setDataSourceFactory(DataSourceFactory dataSourceFactory) { + Assert.notNull(dataSourceFactory, "DataSourceFactory is required"); + this.databaseFactory.setDataSourceFactory(dataSourceFactory); return this; } /** - * Add default scripts to execute to populate the database. + * Add default SQL scripts to execute to populate the database. *

The default scripts are {@code "schema.sql"} to create the database * schema and {@code "data.sql"} to populate the database with data. * @return {@code this}, to facilitate method chaining */ public EmbeddedDatabaseBuilder addDefaultScripts() { - addScript("schema.sql"); - addScript("data.sql"); + return addScripts("schema.sql", "data.sql"); + } + + /** + * Add an SQL script to execute to initialize or populate the database. + * @param script the script to execute + * @return {@code this}, to facilitate method chaining + */ + public EmbeddedDatabaseBuilder addScript(String script) { + this.databasePopulator.addScript(this.resourceLoader.getResource(script)); + return this; + } + + /** + * Add multiple SQL scripts to execute to initialize or populate the database. + * @param scripts the scripts to execute + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder addScripts(String... scripts) { + for (String script : scripts) { + addScript(script); + } + return this; + } + + /** + * Specify the character encoding used in all SQL scripts, if different from + * the platform encoding. + * @param scriptEncoding the encoding used in scripts + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder setScriptEncoding(String scriptEncoding) { + this.databasePopulator.setSqlScriptEncoding(scriptEncoding); + return this; + } + + /** + * Specify the statement separator used in all SQL scripts, if a custom one. + *

Default is ";". + * @param separator the statement separator + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder setSeparator(String separator) { + this.databasePopulator.setSeparator(separator); + return this; + } + + /** + * Specify the single-line comment prefix used in all SQL scripts. + *

Default is "--". + * @param commentPrefix the prefix for single-line comments + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder setCommentPrefix(String commentPrefix) { + this.databasePopulator.setCommentPrefix(commentPrefix); + return this; + } + + /** + * Specify the start delimiter for block comments in all SQL scripts. + *

Default is "/*". + * @param blockCommentStartDelimiter the start delimiter for block comments + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + * @see #setBlockCommentEndDelimiter + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder setBlockCommentStartDelimiter(String blockCommentStartDelimiter) { + this.databasePopulator.setBlockCommentStartDelimiter(blockCommentStartDelimiter); + return this; + } + + /** + * Specify the end delimiter for block comments in all SQL scripts. + *

Default is "*/". + * @param blockCommentEndDelimiter the end delimiter for block comments + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + * @see #setBlockCommentStartDelimiter + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder setBlockCommentEndDelimiter(String blockCommentEndDelimiter) { + this.databasePopulator.setBlockCommentEndDelimiter(blockCommentEndDelimiter); + return this; + } + + /** + * Specify that all failures which occur while executing SQL scripts should + * be logged but should not cause a failure. + *

Defaults to {@code false}. + * @param flag {@code true} if script execution should continue on error + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder continueOnError(boolean flag) { + this.databasePopulator.setContinueOnError(flag); + return this; + } + + /** + * Specify that a failed SQL {@code DROP} statement within an executed + * script can be ignored. + *

This is useful for a database whose SQL dialect does not support an + * {@code IF EXISTS} clause in a {@code DROP} statement. + *

The default is {@code false} so that {@link #build building} will fail + * fast if a script starts with a {@code DROP} statement. + * @param flag {@code true} if failed drop statements should be ignored + * @return {@code this}, to facilitate method chaining + * @since 4.0.3 + */ + public EmbeddedDatabaseBuilder ignoreFailedDrops(boolean flag) { + this.databasePopulator.setIgnoreFailedDrops(flag); return this; } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java index 32fedb2321..f3a43be821 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java @@ -17,74 +17,153 @@ package org.springframework.jdbc.datasource.embedded; import org.junit.Test; - import org.springframework.core.io.ClassRelativeResourceLoader; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.init.CannotReadScriptException; import static org.junit.Assert.*; +import static org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType.*; /** + * Integration tests for {@link EmbeddedDatabaseBuilder}. + * * @author Keith Donald * @author Sam Brannen */ public class EmbeddedDatabaseBuilderTests { - @Test - public void testBuildDefaultScripts() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); - EmbeddedDatabase db = builder.addDefaultScripts().build(); - assertDatabaseCreatedAndShutdown(db); + private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader( + getClass())); - db = builder.addDefaultScripts().build(); - assertDatabaseCreatedAndShutdown(db); - } @Test - public void testBuild() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass())); - EmbeddedDatabase db = builder.addScript("db-schema.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); + public void addDefaultScripts() throws Exception { + doTwice(new Runnable() { - db = builder.addScript("db-schema.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - } - - @Test - public void testBuildWithComments() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass())); - EmbeddedDatabase db = builder.addScript("db-schema-comments.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - - db = builder.addScript("db-schema-comments.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - } - - @Test - public void testBuildH2() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass())); - EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2).addScript("db-schema.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - - db = builder.setType(EmbeddedDatabaseType.H2).addScript("db-schema.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - } - - @Test - public void testBuildDerby() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(getClass())); - EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.DERBY).addScript("db-schema-derby.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); - - db = builder.setType(EmbeddedDatabaseType.DERBY).addScript("db-schema-derby-with-drop.sql").addScript("db-test-data.sql").build(); - assertDatabaseCreatedAndShutdown(db); + @Override + public void run() { + EmbeddedDatabase db = new EmbeddedDatabaseBuilder()// + .addDefaultScripts()// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); } @Test(expected = CannotReadScriptException.class) - public void testBuildNoSuchScript() { + public void addScriptWithBogusFileName() { new EmbeddedDatabaseBuilder().addScript("bogus.sql").build(); } + @Test + public void addScript() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .addScript("db-schema.sql")// + .addScript("db-test-data.sql")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void addScripts() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .addScripts("db-schema.sql", "db-test-data.sql")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void addScriptsWithDefaultCommentPrefix() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .addScripts("db-schema-comments.sql", "db-test-data.sql")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void addScriptsWithCustomCommentPrefix() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .addScripts("db-schema-custom-comments.sql", "db-test-data.sql")// + .setCommentPrefix("~")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void addScriptsWithCustomBlockComments() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .addScripts("db-schema-block-comments.sql", "db-test-data.sql")// + .setBlockCommentStartDelimiter("{*")// + .setBlockCommentEndDelimiter("*}")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void setTypeToH2() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .setType(H2)// + .addScripts("db-schema.sql", "db-test-data.sql")// + .build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + @Test + public void setTypeToDerbyAndIgnoreFailedDrops() throws Exception { + doTwice(new Runnable() { + + @Override + public void run() { + EmbeddedDatabase db = builder// + .setType(DERBY)// + .ignoreFailedDrops(true)// + .addScripts("db-schema-derby-with-drop.sql", "db-test-data.sql").build(); + assertDatabaseCreatedAndShutdown(db); + } + }); + } + + private void doTwice(Runnable test) { + test.run(); + test.run(); + } + private void assertDatabaseCreatedAndShutdown(EmbeddedDatabase db) { JdbcTemplate template = new JdbcTemplate(db); assertEquals("Keith", template.queryForObject("select NAME from T_TEST", String.class)); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBeanTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBeanTests.java index 8844063648..8d9bf3924b 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBeanTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryBeanTests.java @@ -19,8 +19,8 @@ package org.springframework.jdbc.datasource.embedded; import javax.sql.DataSource; import org.junit.Test; - -import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.ClassRelativeResourceLoader; +import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; @@ -31,11 +31,18 @@ import static org.junit.Assert.*; */ public class EmbeddedDatabaseFactoryBeanTests { + private final ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(getClass()); + + + Resource resource(String path) { + return resourceLoader.getResource(path); + } + @Test public void testFactoryBeanLifecycle() throws Exception { EmbeddedDatabaseFactoryBean bean = new EmbeddedDatabaseFactoryBean(); - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator.setScripts(new ClassPathResource("db-schema.sql", getClass()), new ClassPathResource("db-test-data.sql", getClass())); + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(resource("db-schema.sql"), + resource("db-test-data.sql")); bean.setDatabasePopulator(populator); bean.afterPropertiesSet(); DataSource ds = bean.getObject(); diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-block-comments.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-block-comments.sql new file mode 100644 index 0000000000..b0a897339b --- /dev/null +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-block-comments.sql @@ -0,0 +1,9 @@ +{* + these are custom block comments +*} +drop table T_TEST if exists; + +{* + these are custom block comments +*} +create table T_TEST (NAME varchar(50) not null); \ No newline at end of file diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-custom-comments.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-custom-comments.sql new file mode 100644 index 0000000000..cff0cc727e --- /dev/null +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-custom-comments.sql @@ -0,0 +1,5 @@ +~ Failed DROP can be ignored if necessary +drop table T_TEST if exists; + +~ Create the test table +create table T_TEST (NAME varchar(50) not null); \ No newline at end of file diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-derby.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-derby.sql deleted file mode 100644 index 9b2e14d70d..0000000000 --- a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/embedded/db-schema-derby.sql +++ /dev/null @@ -1 +0,0 @@ -create table T_TEST (NAME varchar(50) not null); \ No newline at end of file