Skip to content

Commit b2d83f7

Browse files
committed
This commit adds spring.datasource.lazy-connection property into
a DataSourceProperties class to enable a LazyConnectionDataSourceBeanPostProcessor. BeanPostProcessor wraps an auto-configured DataSource in a LazyConnectionDataSourceProxy. It will be added only if a spring.datasource.lazy-connection property has true and a LazyConnectionDataSourceProxy class is present. The default value of the spring.datasource.lazy-connection property is false, which means a lazy-connection feature is disabled by default. closes gh-15480
1 parent eee07ef commit b2d83f7

File tree

8 files changed

+217
-27
lines changed

8 files changed

+217
-27
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -57,7 +57,8 @@ public class DataSourceAutoConfiguration {
5757
@Configuration
5858
@Conditional(EmbeddedDatabaseCondition.class)
5959
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
60-
@Import(EmbeddedDataSourceConfiguration.class)
60+
@Import({ EmbeddedDataSourceConfiguration.class,
61+
LazyConnectionDataSourceConfiguration.class })
6162
protected static class EmbeddedDatabaseConfiguration {
6263

6364
}
@@ -67,7 +68,8 @@ protected static class EmbeddedDatabaseConfiguration {
6768
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
6869
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
6970
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
70-
DataSourceJmxConfiguration.class })
71+
DataSourceJmxConfiguration.class,
72+
LazyConnectionDataSourceConfiguration.class })
7173
protected static class PooledDataSourceConfiguration {
7274

7375
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -149,6 +149,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
149149
*/
150150
private Charset sqlScriptEncoding;
151151

152+
/**
153+
* Whether datasource must be wrapped in a LazyConnectionDataSourceProxy.
154+
*/
155+
private boolean lazyConnection = false;
156+
152157
private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;
153158

154159
private Xa xa = new Xa();
@@ -478,6 +483,14 @@ public void setXa(Xa xa) {
478483
this.xa = xa;
479484
}
480485

486+
public boolean getLazyConnection() {
487+
return this.lazyConnection;
488+
}
489+
490+
public void setLazyConnection(boolean lazyConnection) {
491+
this.lazyConnection = lazyConnection;
492+
}
493+
481494
/**
482495
* XA Specific datasource settings.
483496
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2019 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+
* http://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.boot.autoconfigure.jdbc;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.config.BeanPostProcessor;
23+
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
24+
25+
/**
26+
* {@link BeanPostProcessor} to wrap a {@link DataSource} with a
27+
* {@link LazyConnectionDataSourceProxy}.
28+
*
29+
* @author Dmytro Nosan
30+
*/
31+
class LazyConnectionDataSourceBeanPostProcessor implements BeanPostProcessor {
32+
33+
@Override
34+
public Object postProcessAfterInitialization(Object bean, String beanName)
35+
throws BeansException {
36+
if (bean instanceof DataSource) {
37+
return new LazyConnectionDataSourceProxy((DataSource) bean);
38+
}
39+
return bean;
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2019 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+
* http://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+
package org.springframework.boot.autoconfigure.jdbc;
17+
18+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
23+
24+
/**
25+
* Configuration to register {@link LazyConnectionDataSourceBeanPostProcessor}, if
26+
* a {@code spring.datasource.lazy-connection} property is present and value is {@code true}.
27+
*
28+
* @author Dmytro Nosan
29+
*/
30+
@Configuration
31+
@ConditionalOnClass(LazyConnectionDataSourceProxy.class)
32+
@ConditionalOnProperty(prefix = "spring.datasource", name = "lazy-connection", havingValue = "true")
33+
class LazyConnectionDataSourceConfiguration {
34+
35+
@Bean
36+
public static LazyConnectionDataSourceBeanPostProcessor lazyConnectionDataSourceBeanPostProcessor() {
37+
return new LazyConnectionDataSourceBeanPostProcessor();
38+
}
39+
40+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -39,6 +39,7 @@
3939
import org.springframework.boot.jdbc.XADataSourceWrapper;
4040
import org.springframework.context.annotation.Bean;
4141
import org.springframework.context.annotation.Configuration;
42+
import org.springframework.context.annotation.Import;
4243
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
4344
import org.springframework.util.Assert;
4445
import org.springframework.util.ClassUtils;
@@ -59,6 +60,7 @@
5960
EmbeddedDatabaseType.class })
6061
@ConditionalOnBean(XADataSourceWrapper.class)
6162
@ConditionalOnMissingBean(DataSource.class)
63+
@Import(LazyConnectionDataSourceConfiguration.class)
6264
public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
6365

6466
private final XADataSourceWrapper wrapper;

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -38,6 +38,7 @@
3838
import org.springframework.beans.factory.BeanCreationException;
3939
import org.springframework.beans.factory.annotation.Autowired;
4040
import org.springframework.boot.autoconfigure.AutoConfigurations;
41+
import org.springframework.boot.jdbc.DataSourceUnwrapper;
4142
import org.springframework.boot.jdbc.DatabaseDriver;
4243
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
4344
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -46,7 +47,9 @@
4647
import org.springframework.context.annotation.Bean;
4748
import org.springframework.context.annotation.Configuration;
4849
import org.springframework.jdbc.core.JdbcTemplate;
50+
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
4951
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
52+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
5053
import org.springframework.util.StringUtils;
5154

5255
import static org.assertj.core.api.Assertions.assertThat;
@@ -104,8 +107,8 @@ public void testBadDriverClass() {
104107
public void hikariValidatesConnectionByDefault() {
105108
assertDataSource(HikariDataSource.class,
106109
Collections.singletonList("org.apache.tomcat"), (dataSource) ->
107-
// Use Connection#isValid()
108-
assertThat(dataSource.getConnectionTestQuery()).isNull());
110+
// Use Connection#isValid()
111+
assertThat(dataSource.getConnectionTestQuery()).isNull());
109112
}
110113

111114
@Test
@@ -140,7 +143,7 @@ public void commonsDbcp2ValidatesConnectionByDefault() {
140143
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> {
141144
assertThat(dataSource.getTestOnBorrow()).isTrue();
142145
assertThat(dataSource.getValidationQuery()).isNull(); // Use
143-
// Connection#isValid()
146+
// Connection#isValid()
144147
});
145148
}
146149

@@ -150,12 +153,12 @@ public void testEmbeddedTypeDefaultsUsername() {
150153
this.contextRunner.withPropertyValues(
151154
"spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
152155
"spring.datasource.url:jdbc:hsqldb:mem:testdb").run((context) -> {
153-
DataSource bean = context.getBean(DataSource.class);
154-
HikariDataSource pool = (HikariDataSource) bean;
155-
assertThat(pool.getDriverClassName())
156-
.isEqualTo("org.hsqldb.jdbcDriver");
157-
assertThat(pool.getUsername()).isEqualTo("sa");
158-
});
156+
DataSource bean = context.getBean(DataSource.class);
157+
HikariDataSource pool = (HikariDataSource) bean;
158+
assertThat(pool.getDriverClassName())
159+
.isEqualTo("org.hsqldb.jdbcDriver");
160+
assertThat(pool.getUsername()).isEqualTo("sa");
161+
});
159162
}
160163

161164
/**
@@ -199,12 +202,12 @@ public void testExplicitDriverClassClearsUsername() {
199202
this.contextRunner.withPropertyValues(
200203
"spring.datasource.driverClassName:" + DatabaseTestDriver.class.getName(),
201204
"spring.datasource.url:jdbc:foo://localhost").run((context) -> {
202-
assertThat(context).hasSingleBean(DataSource.class);
203-
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
204-
assertThat(dataSource.getDriverClassName())
205-
.isEqualTo(DatabaseTestDriver.class.getName());
206-
assertThat(dataSource.getUsername()).isNull();
207-
});
205+
assertThat(context).hasSingleBean(DataSource.class);
206+
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
207+
assertThat(dataSource.getDriverClassName())
208+
.isEqualTo(DatabaseTestDriver.class.getName());
209+
assertThat(dataSource.getUsername()).isNull();
210+
});
208211
}
209212

210213
@Test
@@ -221,7 +224,65 @@ public void testDataSourceIsInitializedEarly() {
221224
.withPropertyValues("spring.datasource.initialization-mode=always")
222225
.run((context) -> assertThat(context
223226
.getBean(TestInitializedDataSourceConfiguration.class).called)
224-
.isTrue());
227+
.isTrue());
228+
}
229+
230+
@Test
231+
public void ignoreLazyConnectionOverriddenDataSource() {
232+
this.contextRunner.withPropertyValues("spring.datasource.lazy-connection=true")
233+
.withUserConfiguration(TestDataSourceConfiguration.class)
234+
.run((context) -> assertThat(context).getBean(DataSource.class)
235+
.isInstanceOf(BasicDataSource.class));
236+
}
237+
238+
@Test
239+
public void lazyConnectionEmbeddedDataSource() {
240+
assertLazyDataSource(EmbeddedDatabase.class,
241+
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat",
242+
"org.apache.commons.dbcp2"),
243+
(dataSource) -> assertThat(DataSourceUnwrapper.unwrap(dataSource,
244+
SimpleDriverDataSource.class)).isNotNull()
245+
.extracting(SimpleDriverDataSource::getUrl).asString()
246+
.startsWith("jdbc:h2:mem:testdb"));
247+
}
248+
249+
@Test
250+
public void lazyConnectionDefaultDataSource() {
251+
assertLazyDataSource(HikariDataSource.class, Collections.emptyList(),
252+
(dataSource) -> assertThat(dataSource.getJdbcUrl())
253+
.startsWith("jdbc:hsqldb:mem:testdb"));
254+
}
255+
256+
@Test
257+
public void lazyConnectionTomcatIsFallback() {
258+
assertLazyDataSource(org.apache.tomcat.jdbc.pool.DataSource.class,
259+
Collections.singletonList("com.zaxxer.hikari"),
260+
(dataSource) -> assertThat(dataSource.getUrl())
261+
.startsWith("jdbc:hsqldb:mem:testdb"));
262+
}
263+
264+
@Test
265+
public void lazyConnectionCommonsDbcp2IsFallback() {
266+
assertLazyDataSource(BasicDataSource.class,
267+
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"),
268+
(dataSource) -> assertThat(dataSource.getUrl())
269+
.startsWith("jdbc:hsqldb:mem:testdb"));
270+
}
271+
272+
private <T extends DataSource> void assertLazyDataSource(Class<T> expectedType,
273+
List<String> hiddenPackages, Consumer<T> consumer) {
274+
FilteredClassLoader classLoader = new FilteredClassLoader(
275+
StringUtils.toStringArray(hiddenPackages));
276+
this.contextRunner.withPropertyValues("spring.datasource.lazy-connection=true")
277+
.withClassLoader(classLoader).run((context) -> {
278+
DataSource dataSource = context.getBean(DataSource.class);
279+
assertThat(dataSource)
280+
.isInstanceOf(LazyConnectionDataSourceProxy.class);
281+
DataSource targetDataSource = ((LazyConnectionDataSourceProxy) dataSource)
282+
.getTargetDataSource();
283+
assertThat(targetDataSource).isInstanceOf(expectedType);
284+
consumer.accept(expectedType.cast(targetDataSource));
285+
});
225286
}
226287

227288
private <T extends DataSource> void assertDataSource(Class<T> expectedType,

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.boot.autoconfigure.jdbc;
1818

19+
import java.sql.Connection;
20+
1921
import javax.sql.DataSource;
2022
import javax.sql.XADataSource;
2123

@@ -28,8 +30,10 @@
2830
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2931
import org.springframework.context.annotation.Bean;
3032
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
3134

3235
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.doReturn;
3337
import static org.mockito.Mockito.mock;
3438

3539
/**
@@ -39,6 +43,17 @@
3943
*/
4044
public class XADataSourceAutoConfigurationTests {
4145

46+
@Test
47+
public void lazyXaDataSource() {
48+
ApplicationContext context = createContext(WrapExisting.class,
49+
"spring.datasource.lazy-connection=true");
50+
assertThat(context.getBean(DataSource.class))
51+
.isInstanceOf(LazyConnectionDataSourceProxy.class);
52+
XADataSource source = context.getBean(XADataSource.class);
53+
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
54+
assertThat(wrapper.getXaDataSource()).isEqualTo(source);
55+
}
56+
4257
@Test
4358
public void wrapExistingXaDataSource() {
4459
ApplicationContext context = createContext(WrapExisting.class);
@@ -111,9 +126,11 @@ private static class MockXADataSourceWrapper implements XADataSourceWrapper {
111126
private XADataSource dataSource;
112127

113128
@Override
114-
public DataSource wrapDataSource(XADataSource dataSource) {
129+
public DataSource wrapDataSource(XADataSource dataSource) throws Exception {
115130
this.dataSource = dataSource;
116-
return mock(DataSource.class);
131+
DataSource mockDataSource = mock(DataSource.class);
132+
doReturn(mock(Connection.class)).when(mockDataSource).getConnection();
133+
return mockDataSource;
117134
}
118135

119136
public XADataSource getXaDataSource() {

0 commit comments

Comments
 (0)