diff --git a/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc b/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc index f9855a33c84..238579144ad 100644 --- a/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc +++ b/framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc @@ -168,6 +168,74 @@ attribute of the `embedded-database` tag to `DERBY`. If you use the builder API, call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`. +[[jdbc-embedded-database-types-custom]] +== Customizing the Embedded Database Type + +While each supported type comes with default connection settings, it is possible +to customize them if necessary. The following example uses H2 with a custom driver: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @Configuration + public class DataSourceConfig { + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .setDatabaseConfigurer(EmbeddedDatabaseConfigurers + .customizeConfigurer(H2, this::customize)) + .addScript("schema.sql") + .build(); + } + + private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) { + return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) { + @Override + public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { + super.configureConnectionProperties(properties, databaseName); + properties.setDriverClass(CustomDriver.class); + } + }; + } + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @Configuration + class DataSourceConfig { + + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setDatabaseConfigurer(EmbeddedDatabaseConfigurers + .customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) }) + .addScript("schema.sql") + .build() + } + + private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer { + return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) { + override fun configureConnectionProperties( + properties: ConnectionProperties, + databaseName: String + ) { + super.configureConnectionProperties(properties, databaseName) + properties.setDriverClass(CustomDriver::class.java) + } + } + } + } +---- +====== + + [[jdbc-embedded-database-dao-testing]] == Testing Data Access Logic with an Embedded Database 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 9e021a6e34a..ea5558d693d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -112,7 +112,8 @@ public class EmbeddedDatabaseBuilder { } /** - * Set the type of embedded database. + * Set the type of embedded database. Consider using {@link #setDatabaseConfigurer} + * if customization of the connections properties is necessary. *
Defaults to HSQL if not called.
* @param databaseType the type of embedded database to build
* @return {@code this}, to facilitate method chaining
@@ -122,6 +123,19 @@ public class EmbeddedDatabaseBuilder {
return this;
}
+ /**
+ * Set the {@linkplain EmbeddedDatabaseConfigurer configurer} to use to
+ * configure the embedded database, as an alternative to {@link #setType}.
+ * @param configurer the configurer of the embedded database
+ * @return {@code this}, to facilitate method chaining
+ * @since 6.2
+ * @see EmbeddedDatabaseConfigurers
+ */
+ public EmbeddedDatabaseBuilder setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
+ this.databaseFactory.setDatabaseConfigurer(configurer);
+ return this;
+ }
+
/**
* Set the factory to use to create the {@link DataSource} instance that
* connects to the embedded database.
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerDelegate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerDelegate.java
new file mode 100644
index 00000000000..252757868e1
--- /dev/null
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerDelegate.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2024 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
+ *
+ * https://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.jdbc.datasource.embedded;
+
+/**
+ * A {@link EmbeddedDatabaseConfigurer} delegate that can be used to customize
+ * the embedded database.
+ *
+ * @author Stephane Nicoll
+ * @since 6.2
+ */
+public class EmbeddedDatabaseConfigurerDelegate extends AbstractEmbeddedDatabaseConfigurer {
+
+ private final EmbeddedDatabaseConfigurer target;
+
+ public EmbeddedDatabaseConfigurerDelegate(EmbeddedDatabaseConfigurer target) {
+ this.target = target;
+ }
+
+ @Override
+ public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
+ this.target.configureConnectionProperties(properties, databaseName);
+ }
+
+}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurers.java
similarity index 60%
rename from spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java
rename to spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurers.java
index f9b728acd02..59373ac4aa8 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -16,6 +16,8 @@
package org.springframework.jdbc.datasource.embedded;
+import java.util.function.UnaryOperator;
+
import org.springframework.util.Assert;
/**
@@ -25,28 +27,24 @@ import org.springframework.util.Assert;
* @author Keith Donald
* @author Oliver Gierke
* @author Sam Brannen
- * @since 3.0
+ * @author Stephane Nicoll
+ * @since 6.2
*/
-final class EmbeddedDatabaseConfigurerFactory {
-
- private EmbeddedDatabaseConfigurerFactory() {
- }
-
+public abstract class EmbeddedDatabaseConfigurers {
/**
* Return a configurer instance for the given embedded database type.
- * @param type the embedded database type (HSQL, H2 or Derby)
+ * @param type the {@linkplain EmbeddedDatabaseType embedded database type}
* @return the configurer instance
* @throws IllegalStateException if the driver for the specified database type is not available
*/
- public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) throws IllegalStateException {
+ public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) {
Assert.notNull(type, "EmbeddedDatabaseType is required");
try {
return switch (type) {
case HSQL -> HsqlEmbeddedDatabaseConfigurer.getInstance();
case H2 -> H2EmbeddedDatabaseConfigurer.getInstance();
case DERBY -> DerbyEmbeddedDatabaseConfigurer.getInstance();
- default -> throw new UnsupportedOperationException("Embedded database type [" + type + "] is not supported");
};
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
@@ -54,4 +52,20 @@ final class EmbeddedDatabaseConfigurerFactory {
}
}
+ /**
+ * Customize the default configurer for the given embedded database type. The
+ * {@code customizer} operator typically uses
+ * {@link EmbeddedDatabaseConfigurerDelegate} to customize things as necessary.
+ * @param type the {@linkplain EmbeddedDatabaseType embedded database type}
+ * @param customizer the customizer to return based on the default
+ * @return the customized configurer instance
+ * @throws IllegalStateException if the driver for the specified database type is not available
+ */
+ public static EmbeddedDatabaseConfigurer customizeConfigurer(
+ EmbeddedDatabaseType type, UnaryOperator Call this when you wish to configure one of the pre-supported types.
+ * Call this when you wish to configure one of the pre-supported types
+ * with their default settings.
* Defaults to HSQL.
* @param type the database type
*/
public void setDatabaseType(EmbeddedDatabaseType type) {
- this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type);
+ this.databaseConfigurer = EmbeddedDatabaseConfigurers.getConfigurer(type);
}
/**
* Set the strategy that will be used to configure the embedded database instance.
- * Call this when you wish to use an embedded database type not already supported.
+ * Call this with
+ * {@linkplain EmbeddedDatabaseConfigurers#customizeConfigurer customizeConfigurer}
+ * when you wish to customize the settings of one of the pre-supported types.
+ * Alternatively, use this when you wish to use an embedded database type not
+ * already supported.
+ * @since 6.2
*/
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
this.databaseConfigurer = configurer;
@@ -178,7 +187,7 @@ public class EmbeddedDatabaseFactory {
// Create the embedded database first
if (this.databaseConfigurer == null) {
- this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL);
+ this.databaseConfigurer = EmbeddedDatabaseConfigurers.getConfigurer(EmbeddedDatabaseType.HSQL);
}
this.databaseConfigurer.configureConnectionProperties(
this.dataSourceFactory.getConnectionProperties(), this.databaseName);
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 6dbe8d6f89d..eb1c41d27ac 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
@@ -121,6 +121,23 @@ class EmbeddedDatabaseBuilderTests {
});
}
+ @Test
+ void setTypeConfigurerToCustomH2() {
+ doTwice(() -> {
+ EmbeddedDatabase db = builder
+ .setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(H2, defaultConfigurer ->
+ new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
+ @Override
+ public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
+ super.configureConnectionProperties(properties, databaseName);
+ }
+ }))
+ .addScripts("db-schema.sql", "db-test-data.sql")//
+ .build();
+ assertDatabaseCreatedAndShutdown(db);
+ });
+ }
+
@Test
void setTypeToDerbyAndIgnoreFailedDrops() {
doTwice(() -> {
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java
index ecb41f3e783..2486518b9ed 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java
@@ -17,6 +17,7 @@
package org.springframework.jdbc.datasource.embedded;
import java.sql.Connection;
+import java.sql.SQLException;
import org.junit.jupiter.api.Test;
@@ -25,11 +26,14 @@ import org.springframework.jdbc.datasource.init.DatabasePopulator;
import static org.assertj.core.api.Assertions.assertThat;
/**
+ * Tests for {@link EmbeddedDatabaseFactory}.
+ *
* @author Keith Donald
+ * @author Stephane Nicoll
*/
class EmbeddedDatabaseFactoryTests {
- private EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
+ private final EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
@Test
@@ -41,6 +45,45 @@ class EmbeddedDatabaseFactoryTests {
db.shutdown();
}
+ @Test
+ void customizeConfigurerWithAnotherDatabaseName() throws SQLException {
+ this.factory.setDatabaseName("original-db-mame");
+ this.factory.setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(
+ EmbeddedDatabaseType.H2, defaultConfigurer ->
+ new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
+ @Override
+ public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
+ super.configureConnectionProperties(properties, "custom-db-name");
+ }
+ }));
+ EmbeddedDatabase db = this.factory.getDatabase();
+ try (Connection connection = db.getConnection()) {
+ assertThat(connection.getMetaData().getURL()).contains("custom-db-name")
+ .doesNotContain("original-db-mame");
+ }
+ db.shutdown();
+ }
+
+ @Test
+ void customizeConfigurerWithCustomizedUrl() throws SQLException {
+ this.factory.setDatabaseName("original-db-mame");
+ this.factory.setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(
+ EmbeddedDatabaseType.H2, defaultConfigurer ->
+ new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
+ @Override
+ public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
+ super.configureConnectionProperties(properties, databaseName);
+ properties.setUrl("jdbc:h2:mem:custom-db-name;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MariaDB");
+ }
+ }));
+ EmbeddedDatabase db = this.factory.getDatabase();
+ try (Connection connection = db.getConnection()) {
+ assertThat(connection.getMetaData().getURL()).contains("custom-db-name")
+ .doesNotContain("original-db-mame");
+ }
+ db.shutdown();
+ }
+
private static class StubDatabasePopulator implements DatabasePopulator {