Skip to content

Commit 607e4bb

Browse files
committed
Merge pull request #50271 from vpavic
* improve-datasourcebuilder: Polish "Handle lazy connection proxy datasource in DataSourceBuilder" Handle lazy connection proxy datasource in DataSourceBuilder Closes gh-50271
2 parents 96a36cf + 627ac04 commit 607e4bb

5 files changed

Lines changed: 109 additions & 17 deletions

File tree

module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,7 @@ public static DataSourceBuilder<?> create(@Nullable ClassLoader classLoader) {
237237
* @return a new {@link DataSource} builder
238238
*/
239239
public static DataSourceBuilder<?> derivedFrom(DataSource dataSource) {
240-
return new DataSourceBuilder<>(unwrap(dataSource));
241-
}
242-
243-
private static DataSource unwrap(DataSource dataSource) {
244-
try {
245-
while (dataSource.isWrapperFor(DataSource.class)) {
246-
DataSource unwrapped = dataSource.unwrap(DataSource.class);
247-
if (unwrapped == dataSource) {
248-
return unwrapped;
249-
}
250-
dataSource = unwrapped;
251-
}
252-
}
253-
catch (SQLException ex) {
254-
// Try to continue with the existing, potentially still wrapped, DataSource
255-
}
256-
return dataSource;
240+
return new DataSourceBuilder<>(DataSourceUnwrapper.unwrapRoot(dataSource));
257241
}
258242

259243
/**

module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceUnwrapper.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,37 @@ private DataSourceUnwrapper() {
9393
return unwrap(dataSource, target, target);
9494
}
9595

96+
/**
97+
* Return the root {@link DataSource} by recursively unwrapping all
98+
* {@link org.springframework.jdbc.datasource.DelegatingDataSource delegating}, proxy,
99+
* and {@link java.sql.Wrapper} layers until no further unwrapping is possible.
100+
* @param dataSource the datasource to unwrap
101+
* @return the root {@link DataSource}
102+
* @since 4.1.0
103+
*/
104+
public static DataSource unwrapRoot(DataSource dataSource) {
105+
if (DELEGATING_DATA_SOURCE_PRESENT) {
106+
DataSource targetDataSource = DelegatingDataSourceUnwrapper.getTargetDataSource(dataSource);
107+
if (targetDataSource != null) {
108+
return unwrapRoot(targetDataSource);
109+
}
110+
}
111+
if (AopUtils.isAopProxy(dataSource)) {
112+
Object proxyTarget = AopProxyUtils.getSingletonTarget(dataSource);
113+
if (proxyTarget instanceof DataSource proxyDataSource) {
114+
return unwrapRoot(proxyDataSource);
115+
}
116+
}
117+
DataSource unwrapped = safeUnwrap(dataSource);
118+
if (unwrapped != null) {
119+
if (unwrapped == dataSource) {
120+
return unwrapped;
121+
}
122+
return unwrapRoot(unwrapped);
123+
}
124+
return dataSource;
125+
}
126+
96127
private static <S> @Nullable S safeUnwrap(Wrapper wrapper, Class<S> target) {
97128
try {
98129
if (target.isInterface() && wrapper.isWrapperFor(target)) {
@@ -105,6 +136,18 @@ private DataSourceUnwrapper() {
105136
return null;
106137
}
107138

139+
private static @Nullable DataSource safeUnwrap(DataSource dataSource) {
140+
try {
141+
if (dataSource.isWrapperFor(DataSource.class)) {
142+
return dataSource.unwrap(DataSource.class);
143+
}
144+
}
145+
catch (Exception ex) {
146+
// continue
147+
}
148+
return null;
149+
}
150+
108151
private static final class DelegatingDataSourceUnwrapper {
109152

110153
private static @Nullable DataSource getTargetDataSource(DataSource dataSource) {

module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.vibur.dbcp.ViburDBCPDataSource;
4747

4848
import org.springframework.jdbc.datasource.AbstractDataSource;
49+
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
4950
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
5051
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
5152
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@@ -61,6 +62,7 @@
6162
* @author Stephane Nicoll
6263
* @author Fabio Grassi
6364
* @author Phillip Webb
65+
* @author Vedran Pavic
6466
*/
6567
class DataSourceBuilderTests {
6668

@@ -427,6 +429,19 @@ void buildWhenDerivedFromWrappedDataSource() {
427429
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
428430
}
429431

432+
@Test
433+
void buildWhenDerivedFromLazyConnectionDataSourceProxy() {
434+
HikariDataSource dataSource = new HikariDataSource();
435+
dataSource.setUsername("test");
436+
dataSource.setPassword("secret");
437+
dataSource.setJdbcUrl("jdbc:h2:test");
438+
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(new LazyConnectionDataSourceProxy(dataSource));
439+
HikariDataSource built = (HikariDataSource) builder.username("test2").password("secret2").build();
440+
assertThat(built.getUsername()).isEqualTo("test2");
441+
assertThat(built.getPassword()).isEqualTo("secret2");
442+
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
443+
}
444+
430445
@Test // gh-26644
431446
void buildWhenDerivedFromExistingDatabaseWithTypeChange() {
432447
HikariDataSource dataSource = new HikariDataSource();

module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/DataSourceUnwrapperTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,40 @@ void unwrapDataSourceProxy() {
106106
.isSameAs(dataSource);
107107
}
108108

109+
@Test
110+
void unwrapRootWithPlainDataSource() {
111+
DataSource dataSource = new HikariDataSource();
112+
assertThat(DataSourceUnwrapper.unwrapRoot(dataSource)).isSameAs(dataSource);
113+
}
114+
115+
@Test
116+
void unwrapRootWithDelegate() {
117+
DataSource dataSource = new HikariDataSource();
118+
DataSource actual = wrapInDelegate(wrapInDelegate(dataSource));
119+
assertThat(DataSourceUnwrapper.unwrapRoot(actual)).isSameAs(dataSource);
120+
}
121+
122+
@Test
123+
void unwrapRootWithProxy() {
124+
DataSource dataSource = new HikariDataSource();
125+
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
126+
assertThat(DataSourceUnwrapper.unwrapRoot(actual)).isSameAs(dataSource);
127+
}
128+
129+
@Test
130+
void unwrapRootWithLazyConnectionDataSource() {
131+
DataSource dataSource = new HikariDataSource();
132+
DataSource actual = new LazyConnectionDataSourceProxy(dataSource);
133+
assertThat(DataSourceUnwrapper.unwrapRoot(actual)).isSameAs(dataSource);
134+
}
135+
136+
@Test
137+
void unwrapRootWithSeveralLevelOfWrapping() {
138+
DataSource dataSource = new HikariDataSource();
139+
DataSource actual = wrapInProxy(wrapInDelegate(wrapInDelegate(wrapInProxy(wrapInDelegate(dataSource)))));
140+
assertThat(DataSourceUnwrapper.unwrapRoot(actual)).isSameAs(dataSource);
141+
}
142+
109143
@Test
110144
void unwrappingIsNotAttemptedWhenTargetIsNotAnInterface() {
111145
DataSource dataSource = mock(DataSource.class);

module/spring-boot-liquibase/src/test/java/org/springframework/boot/liquibase/autoconfigure/LiquibaseAutoConfigurationTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.beans.factory.config.BeanDefinition;
5252
import org.springframework.boot.autoconfigure.AutoConfigurations;
5353
import org.springframework.boot.jdbc.DataSourceBuilder;
54+
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
5455
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
5556
import org.springframework.boot.jdbc.autoconfigure.JdbcConnectionDetails;
5657
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
@@ -90,6 +91,7 @@
9091
* @author Moritz Halbritter
9192
* @author Phillip Webb
9293
* @author Ahmed Ashour
94+
* @author Vedran Pavic
9395
*/
9496
@ExtendWith(OutputCaptureExtension.class)
9597
class LiquibaseAutoConfigurationTests {
@@ -373,6 +375,20 @@ void overrideUser() {
373375
}));
374376
}
375377

378+
@Test
379+
@WithDbChangelogMasterYamlResource
380+
void lazyConnectionDataSource() {
381+
String jdbcUrl = "jdbc:h2:mem:liquibase-" + UUID.randomUUID();
382+
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
383+
.withPropertyValues("spring.datasource.url:" + jdbcUrl, "spring.datasource.username:not-sa",
384+
"spring.datasource.connection-fetch:lazy", "spring.liquibase.user:sa")
385+
.run(assertLiquibase((liquibase) -> {
386+
SimpleDriverDataSource dataSource = (SimpleDriverDataSource) liquibase.getDataSource();
387+
assertThat(dataSource.getUrl()).isEqualTo(jdbcUrl);
388+
assertThat(dataSource.getUsername()).isEqualTo("sa");
389+
}));
390+
}
391+
376392
@Test
377393
@WithDbChangelogMasterYamlResource
378394
void overrideUserWhenCustom() {

0 commit comments

Comments
 (0)