Skip to content

Commit 17be905

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 c44a1e1 commit 17be905

File tree

8 files changed

+202
-11
lines changed

8 files changed

+202
-11
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,41 @@
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 a
26+
* {@code spring.datasource.lazy-connection} property is present and value is
27+
* {@code true}.
28+
*
29+
* @author Dmytro Nosan
30+
*/
31+
@Configuration
32+
@ConditionalOnClass(LazyConnectionDataSourceProxy.class)
33+
@ConditionalOnProperty(prefix = "spring.datasource", name = "lazy-connection", havingValue = "true")
34+
class LazyConnectionDataSourceConfiguration {
35+
36+
@Bean
37+
public static LazyConnectionDataSourceBeanPostProcessor lazyConnectionDataSourceBeanPostProcessor() {
38+
return new LazyConnectionDataSourceBeanPostProcessor();
39+
}
40+
41+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 ClassLoader classLoader;

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

Lines changed: 63 additions & 2 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;
@@ -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

@@ -224,6 +227,64 @@ public void testDataSourceIsInitializedEarly() {
224227
.isTrue());
225228
}
226229

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+
});
286+
}
287+
227288
private <T extends DataSource> void assertDataSource(Class<T> expectedType,
228289
List<String> hiddenPackages, Consumer<T> consumer) {
229290
FilteredClassLoader classLoader = new FilteredClassLoader(

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() {

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3631,7 +3631,22 @@ TIP: See <<howto.adoc#howto-configure-a-datasource,the "`How-to`" section>> for
36313631
advanced examples, typically to take full control over the configuration of the
36323632
DataSource.
36333633

3634+
[[boot-features-configure-lazy-datasource]]
3635+
=== Configure a lazy DataSource
3636+
Lazy `javax.sql.DataSource` (`org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy`) allows delaying the obtaining of a `javax.sql.Connection` until a `javax.sql.Connection` is really required.
3637+
JDBC transaction control can happen without fetching a `javax.sql.Connection` from the pool or communicating with the database.
36343638

3639+
`Connection` initialization properties like `auto-commit`, `transaction-isolation` and `read-only`
3640+
will be kept and applied to the actual JDBC `javax.sql.Connection` as soon as an actual `Connection` is fetched.
3641+
Consequently, _commit_ and _rollback_ calls will be *ignored* if no Statements have been created.
3642+
3643+
To *enable* this feature `spring.datasource.lazy-connection=true` must be set, by default this feature is disabled.
3644+
3645+
[NOTE]
3646+
====
3647+
Lazy `javax.sql.DataSource` proxy needs to return wrapped Connections in order to handle lazy fetching of an actual JDBC `Connection`.
3648+
Use `javax.sql.Connection#unwrap` or `static org.springframework.boot.jdbc.DataSourceUnwrapper#unwrap` to retrieve the native JDBC `Connection`.
3649+
====
36353650

36363651
[[boot-features-embedded-database-support]]
36373652
==== Embedded Database Support
@@ -3755,8 +3770,6 @@ example:
37553770
spring.datasource.tomcat.test-on-borrow=true
37563771
----
37573772

3758-
3759-
37603773
[[boot-features-connecting-to-a-jndi-datasource]]
37613774
==== Connection to a JNDI DataSource
37623775
If you deploy your Spring Boot application to an Application Server, you might want to

0 commit comments

Comments
 (0)