Skip to content

Commit ab06d10

Browse files
committed
Fix checkpoint-restore with replaced or wrapped HikariDataSource
Closes gh-37580
1 parent ee9c745 commit ab06d10

File tree

4 files changed

+62
-6
lines changed

4 files changed

+62
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetai
126126

127127
@Bean
128128
@ConditionalOnCheckpointRestore
129-
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(HikariDataSource hikariDataSource) {
129+
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(DataSource hikariDataSource) {
130130
return new HikariCheckpointRestoreLifecycle(hikariDataSource);
131131
}
132132

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
2222
import org.assertj.core.api.InstanceOfAssertFactories;
2323
import org.junit.jupiter.api.Test;
2424

25+
import org.springframework.beans.BeansException;
26+
import org.springframework.beans.factory.config.BeanPostProcessor;
2527
import org.springframework.boot.autoconfigure.AutoConfigurations;
2628
import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle;
2729
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2830
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
2931
import org.springframework.context.annotation.Bean;
3032
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.jdbc.datasource.DelegatingDataSource;
3134

3235
import static org.assertj.core.api.Assertions.assertThat;
3336

@@ -131,6 +134,14 @@ void whenCheckpointRestoreIsAvailableHikariAutoConfigRegistersLifecycleBean() {
131134
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
132135
}
133136

137+
@Test
138+
@ClassPathOverrides("org.crac:crac:1.3.0")
139+
void whenCheckpointRestoreIsAvailableAndDataSourceHasBeenWrappedHikariAutoConfigRegistersLifecycleBean() {
140+
this.contextRunner.withUserConfiguration(DataSourceWrapperConfiguration.class)
141+
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName())
142+
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
143+
}
144+
134145
@Test
135146
void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() {
136147
this.contextRunner.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName())
@@ -147,4 +158,24 @@ JdbcConnectionDetails sqlConnectionDetails() {
147158

148159
}
149160

161+
@Configuration(proxyBeanMethods = false)
162+
static class DataSourceWrapperConfiguration {
163+
164+
@Bean
165+
static BeanPostProcessor dataSourceWrapper() {
166+
return new BeanPostProcessor() {
167+
168+
@Override
169+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
170+
if (bean instanceof DataSource dataSource) {
171+
return new DelegatingDataSource(dataSource);
172+
}
173+
return bean;
174+
}
175+
176+
};
177+
}
178+
179+
}
180+
150181
}

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.concurrent.TimeoutException;
2626
import java.util.function.Function;
2727

28+
import javax.sql.DataSource;
29+
2830
import com.zaxxer.hikari.HikariConfigMXBean;
2931
import com.zaxxer.hikari.HikariDataSource;
3032
import com.zaxxer.hikari.HikariPoolMXBean;
@@ -71,10 +73,12 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
7173

7274
/**
7375
* Creates a new {@code HikariCheckpointRestoreLifecycle} that will allow the given
74-
* {@code dataSource} to participate in checkpoint-restore.
76+
* {@code dataSource} to participate in checkpoint-restore. The {@code dataSource} is
77+
* {@link DataSourceUnwrapper#unwrap unwrapped} to a {@link HikariDataSource}. If such
78+
* unwrapping is not possible, the lifecycle will have no effect.
7579
* @param dataSource the checkpoint-restore participant
7680
*/
77-
public HikariCheckpointRestoreLifecycle(HikariDataSource dataSource) {
81+
public HikariCheckpointRestoreLifecycle(DataSource dataSource) {
7882
this.dataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class);
7983
this.hasOpenConnections = (pool) -> {
8084
ThreadPoolExecutor closeConnectionExecutor = (ThreadPoolExecutor) ReflectionUtils
@@ -86,7 +90,7 @@ public HikariCheckpointRestoreLifecycle(HikariDataSource dataSource) {
8690

8791
@Override
8892
public void start() {
89-
if (this.dataSource.isRunning()) {
93+
if (this.dataSource == null || this.dataSource.isRunning()) {
9094
return;
9195
}
9296
Assert.state(!this.dataSource.isClosed(), "DataSource has been closed and cannot be restarted");
@@ -98,7 +102,7 @@ public void start() {
98102

99103
@Override
100104
public void stop() {
101-
if (!this.dataSource.isRunning()) {
105+
if (this.dataSource == null || !this.dataSource.isRunning()) {
102106
return;
103107
}
104108
if (this.dataSource.isAllowPoolSuspension()) {
@@ -143,7 +147,7 @@ private void waitForConnectionsToClose() {
143147

144148
@Override
145149
public boolean isRunning() {
146-
return this.dataSource.isRunning();
150+
return this.dataSource != null && this.dataSource.isRunning();
147151
}
148152

149153
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818

1919
import java.util.UUID;
2020

21+
import javax.sql.DataSource;
22+
2123
import com.zaxxer.hikari.HikariConfig;
2224
import com.zaxxer.hikari.HikariDataSource;
2325
import org.junit.jupiter.api.Test;
2426

2527
import static org.assertj.core.api.Assertions.assertThat;
2628
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2729
import static org.assertj.core.api.Assertions.assertThatNoException;
30+
import static org.mockito.Mockito.mock;
2831

2932
/**
3033
* Tests for {@link HikariCheckpointRestoreLifecycle}.
@@ -82,4 +85,22 @@ void whenDataSourceIsClosedThenStartShouldThrow() {
8285
assertThatExceptionOfType(RuntimeException.class).isThrownBy(this.lifecycle::start);
8386
}
8487

88+
@Test
89+
void startHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
90+
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
91+
mock(DataSource.class));
92+
assertThat(nonHikariLifecycle.isRunning()).isFalse();
93+
nonHikariLifecycle.start();
94+
assertThat(nonHikariLifecycle.isRunning()).isFalse();
95+
}
96+
97+
@Test
98+
void stopHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
99+
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
100+
mock(DataSource.class));
101+
assertThat(nonHikariLifecycle.isRunning()).isFalse();
102+
nonHikariLifecycle.stop();
103+
assertThat(nonHikariLifecycle.isRunning()).isFalse();
104+
}
105+
85106
}

0 commit comments

Comments
 (0)