Skip to content

Commit f44104a

Browse files
committed
Allow customizations of embedded database connections
This commit allows EmbeddedDatabaseConfigurer instances to be further customized if necessary. EmbeddedDatabaseBuilder has a way now to set a DatabaseConfigurer rather than just a type to provide full control, or customize an existing supported database type using the new EmbeddedDatabaseConfigurers#customizeConfigurer callback. Closes spring-projectsgh-21160
1 parent a4db0e7 commit f44104a

File tree

7 files changed

+224
-20
lines changed

7 files changed

+224
-20
lines changed

Diff for: framework-docs/modules/ROOT/pages/data-access/jdbc/embedded-database-support.adoc

+68
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,74 @@ attribute of the `embedded-database` tag to `DERBY`. If you use the builder API,
168168
call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`.
169169

170170

171+
[[jdbc-embedded-database-types-custom]]
172+
== Customizing the Embedded Database Type
173+
174+
While each supported type comes with default connection settings, it is possible
175+
to customize them if necessary. The following example uses H2 with a custom driver:
176+
177+
[tabs]
178+
======
179+
Java::
180+
+
181+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
182+
----
183+
@Configuration
184+
public class DataSourceConfig {
185+
186+
@Bean
187+
public DataSource dataSource() {
188+
return new EmbeddedDatabaseBuilder()
189+
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
190+
.customizeConfigurer(H2, this::customize))
191+
.addScript("schema.sql")
192+
.build();
193+
}
194+
195+
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
196+
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
197+
@Override
198+
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
199+
super.configureConnectionProperties(properties, databaseName);
200+
properties.setDriverClass(CustomDriver.class);
201+
}
202+
};
203+
}
204+
}
205+
----
206+
207+
Kotlin::
208+
+
209+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
210+
----
211+
@Configuration
212+
class DataSourceConfig {
213+
214+
@Bean
215+
fun dataSource(): DataSource {
216+
return EmbeddedDatabaseBuilder()
217+
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
218+
.customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) })
219+
.addScript("schema.sql")
220+
.build()
221+
}
222+
223+
private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer {
224+
return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
225+
override fun configureConnectionProperties(
226+
properties: ConnectionProperties,
227+
databaseName: String
228+
) {
229+
super.configureConnectionProperties(properties, databaseName)
230+
properties.setDriverClass(CustomDriver::class.java)
231+
}
232+
}
233+
}
234+
}
235+
----
236+
======
237+
238+
171239
[[jdbc-embedded-database-dao-testing]]
172240
== Testing Data Access Logic with an Embedded Database
173241

Diff for: spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -112,7 +112,8 @@ public EmbeddedDatabaseBuilder setName(String databaseName) {
112112
}
113113

114114
/**
115-
* Set the type of embedded database.
115+
* Set the type of embedded database. Consider using {@link #setDatabaseConfigurer}
116+
* if customization of the connections properties is necessary.
116117
* <p>Defaults to HSQL if not called.
117118
* @param databaseType the type of embedded database to build
118119
* @return {@code this}, to facilitate method chaining
@@ -122,6 +123,19 @@ public EmbeddedDatabaseBuilder setType(EmbeddedDatabaseType databaseType) {
122123
return this;
123124
}
124125

126+
/**
127+
* Set the {@linkplain EmbeddedDatabaseConfigurer configurer} to use to
128+
* configure the embedded database, as an alternative to {@link #setType}.
129+
* @param configurer the configurer of the embedded database
130+
* @return {@code this}, to facilitate method chaining
131+
* @since 6.2
132+
* @see EmbeddedDatabaseConfigurers
133+
*/
134+
public EmbeddedDatabaseBuilder setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
135+
this.databaseFactory.setDatabaseConfigurer(configurer);
136+
return this;
137+
}
138+
125139
/**
126140
* Set the factory to use to create the {@link DataSource} instance that
127141
* connects to the embedded database.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.datasource.embedded;
18+
19+
/**
20+
* A {@link EmbeddedDatabaseConfigurer} delegate that can be used to customize
21+
* the embedded database.
22+
*
23+
* @author Stephane Nicoll
24+
* @since 6.2
25+
*/
26+
public class EmbeddedDatabaseConfigurerDelegate extends AbstractEmbeddedDatabaseConfigurer {
27+
28+
private final EmbeddedDatabaseConfigurer target;
29+
30+
public EmbeddedDatabaseConfigurerDelegate(EmbeddedDatabaseConfigurer target) {
31+
this.target = target;
32+
}
33+
34+
@Override
35+
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
36+
this.target.configureConnectionProperties(properties, databaseName);
37+
}
38+
39+
}

Diff for: spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurerFactory.java renamed to spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseConfigurers.java

+24-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.jdbc.datasource.embedded;
1818

19+
import java.util.function.UnaryOperator;
20+
1921
import org.springframework.util.Assert;
2022

2123
/**
@@ -25,33 +27,45 @@
2527
* @author Keith Donald
2628
* @author Oliver Gierke
2729
* @author Sam Brannen
28-
* @since 3.0
30+
* @author Stephane Nicoll
31+
* @since 6.2
2932
*/
30-
final class EmbeddedDatabaseConfigurerFactory {
31-
32-
private EmbeddedDatabaseConfigurerFactory() {
33-
}
34-
33+
public abstract class EmbeddedDatabaseConfigurers {
3534

3635
/**
3736
* Return a configurer instance for the given embedded database type.
38-
* @param type the embedded database type (HSQL, H2 or Derby)
37+
* @param type the {@linkplain EmbeddedDatabaseType embedded database type}
3938
* @return the configurer instance
4039
* @throws IllegalStateException if the driver for the specified database type is not available
4140
*/
42-
public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) throws IllegalStateException {
41+
public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) {
4342
Assert.notNull(type, "EmbeddedDatabaseType is required");
4443
try {
4544
return switch (type) {
4645
case HSQL -> HsqlEmbeddedDatabaseConfigurer.getInstance();
4746
case H2 -> H2EmbeddedDatabaseConfigurer.getInstance();
4847
case DERBY -> DerbyEmbeddedDatabaseConfigurer.getInstance();
49-
default -> throw new UnsupportedOperationException("Embedded database type [" + type + "] is not supported");
5048
};
5149
}
5250
catch (ClassNotFoundException | NoClassDefFoundError ex) {
5351
throw new IllegalStateException("Driver for test database type [" + type + "] is not available", ex);
5452
}
5553
}
5654

55+
/**
56+
* Customize the default configurer for the given embedded database type. The
57+
* {@code customizer} operator typically uses
58+
* {@link EmbeddedDatabaseConfigurerDelegate} to customize things as necessary.
59+
* @param type the {@linkplain EmbeddedDatabaseType embedded database type}
60+
* @param customizer the customizer to return based on the default
61+
* @return the customized configurer instance
62+
* @throws IllegalStateException if the driver for the specified database type is not available
63+
*/
64+
public static EmbeddedDatabaseConfigurer customizeConfigurer(
65+
EmbeddedDatabaseType type, UnaryOperator<EmbeddedDatabaseConfigurer> customizer) {
66+
67+
EmbeddedDatabaseConfigurer defaultConfigurer = getConfigurer(type);
68+
return customizer.apply(defaultConfigurer);
69+
}
70+
5771
}

Diff for: spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,9 +45,11 @@
4545
* for the database.
4646
* <li>Call {@link #setDatabaseName} to set an explicit name for the database.
4747
* <li>Call {@link #setDatabaseType} to set the database type if you wish to
48-
* use one of the supported types.
48+
* use one of the supported types with their default settings.
4949
* <li>Call {@link #setDatabaseConfigurer} to configure support for a custom
50-
* embedded database type.
50+
* embedded database type, or
51+
* {@linkplain EmbeddedDatabaseConfigurers#customizeConfigurer customize} the
52+
* default of a supported types.
5153
* <li>Call {@link #setDatabasePopulator} to change the algorithm used to
5254
* populate the database.
5355
* <li>Call {@link #setDataSourceFactory} to change the type of
@@ -60,6 +62,7 @@
6062
* @author Keith Donald
6163
* @author Juergen Hoeller
6264
* @author Sam Brannen
65+
* @author Stephane Nicoll
6366
* @since 3.0
6467
*/
6568
public class EmbeddedDatabaseFactory {
@@ -124,17 +127,23 @@ public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
124127

125128
/**
126129
* Set the type of embedded database to use.
127-
* <p>Call this when you wish to configure one of the pre-supported types.
130+
* <p>Call this when you wish to configure one of the pre-supported types
131+
* with their default settings.
128132
* <p>Defaults to HSQL.
129133
* @param type the database type
130134
*/
131135
public void setDatabaseType(EmbeddedDatabaseType type) {
132-
this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type);
136+
this.databaseConfigurer = EmbeddedDatabaseConfigurers.getConfigurer(type);
133137
}
134138

135139
/**
136140
* Set the strategy that will be used to configure the embedded database instance.
137-
* <p>Call this when you wish to use an embedded database type not already supported.
141+
* <p>Call this with
142+
* {@linkplain EmbeddedDatabaseConfigurers#customizeConfigurer customizeConfigurer}
143+
* when you wish to customize the settings of one of the pre-supported types.
144+
* Alternatively, use this when you wish to use an embedded database type not
145+
* already supported.
146+
* @since 6.2
138147
*/
139148
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
140149
this.databaseConfigurer = configurer;
@@ -178,7 +187,7 @@ protected void initDatabase() {
178187

179188
// Create the embedded database first
180189
if (this.databaseConfigurer == null) {
181-
this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL);
190+
this.databaseConfigurer = EmbeddedDatabaseConfigurers.getConfigurer(EmbeddedDatabaseType.HSQL);
182191
}
183192
this.databaseConfigurer.configureConnectionProperties(
184193
this.dataSourceFactory.getConnectionProperties(), this.databaseName);

Diff for: spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseBuilderTests.java

+17
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ void setTypeToH2() {
121121
});
122122
}
123123

124+
@Test
125+
void setTypeConfigurerToCustomH2() {
126+
doTwice(() -> {
127+
EmbeddedDatabase db = builder
128+
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(H2, defaultConfigurer ->
129+
new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
130+
@Override
131+
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
132+
super.configureConnectionProperties(properties, databaseName);
133+
}
134+
}))
135+
.addScripts("db-schema.sql", "db-test-data.sql")//
136+
.build();
137+
assertDatabaseCreatedAndShutdown(db);
138+
});
139+
}
140+
124141
@Test
125142
void setTypeToDerbyAndIgnoreFailedDrops() {
126143
doTwice(() -> {

Diff for: spring-jdbc/src/test/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactoryTests.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.jdbc.datasource.embedded;
1818

1919
import java.sql.Connection;
20+
import java.sql.SQLException;
2021

2122
import org.junit.jupiter.api.Test;
2223

@@ -25,11 +26,14 @@
2526
import static org.assertj.core.api.Assertions.assertThat;
2627

2728
/**
29+
* Tests for {@link EmbeddedDatabaseFactory}.
30+
*
2831
* @author Keith Donald
32+
* @author Stephane Nicoll
2933
*/
3034
class EmbeddedDatabaseFactoryTests {
3135

32-
private EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
36+
private final EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory();
3337

3438

3539
@Test
@@ -41,6 +45,45 @@ void testGetDataSource() {
4145
db.shutdown();
4246
}
4347

48+
@Test
49+
void customizeConfigurerWithAnotherDatabaseName() throws SQLException {
50+
this.factory.setDatabaseName("original-db-mame");
51+
this.factory.setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(
52+
EmbeddedDatabaseType.H2, defaultConfigurer ->
53+
new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
54+
@Override
55+
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
56+
super.configureConnectionProperties(properties, "custom-db-name");
57+
}
58+
}));
59+
EmbeddedDatabase db = this.factory.getDatabase();
60+
try (Connection connection = db.getConnection()) {
61+
assertThat(connection.getMetaData().getURL()).contains("custom-db-name")
62+
.doesNotContain("original-db-mame");
63+
}
64+
db.shutdown();
65+
}
66+
67+
@Test
68+
void customizeConfigurerWithCustomizedUrl() throws SQLException {
69+
this.factory.setDatabaseName("original-db-mame");
70+
this.factory.setDatabaseConfigurer(EmbeddedDatabaseConfigurers.customizeConfigurer(
71+
EmbeddedDatabaseType.H2, defaultConfigurer ->
72+
new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
73+
@Override
74+
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
75+
super.configureConnectionProperties(properties, databaseName);
76+
properties.setUrl("jdbc:h2:mem:custom-db-name;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;MODE=MariaDB");
77+
}
78+
}));
79+
EmbeddedDatabase db = this.factory.getDatabase();
80+
try (Connection connection = db.getConnection()) {
81+
assertThat(connection.getMetaData().getURL()).contains("custom-db-name")
82+
.doesNotContain("original-db-mame");
83+
}
84+
db.shutdown();
85+
}
86+
4487

4588
private static class StubDatabasePopulator implements DatabasePopulator {
4689

0 commit comments

Comments
 (0)