Add nullability annotations to module/spring-boot-jdbc

See gh-46587
This commit is contained in:
Moritz Halbritter
2025-08-01 09:16:11 +02:00
parent 40d0560378
commit dc7f434783
40 changed files with 325 additions and 182 deletions
@@ -35,12 +35,14 @@ import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.h2.jdbcx.JdbcDataSource;
import org.jspecify.annotations.Nullable;
import org.postgresql.ds.PGSimpleDataSource;
import org.vibur.dbcp.ViburDBCPDataSource;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@@ -89,15 +91,15 @@ import org.springframework.util.StringUtils;
*/
public final class DataSourceBuilder<T extends DataSource> {
private final ClassLoader classLoader;
private final @Nullable ClassLoader classLoader;
private final Map<DataSourceProperty, String> values = new HashMap<>();
private Class<T> type;
private @Nullable Class<T> type;
private final DataSource deriveFrom;
private final @Nullable DataSource deriveFrom;
private DataSourceBuilder(ClassLoader classLoader) {
private DataSourceBuilder(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
this.deriveFrom = null;
}
@@ -117,7 +119,7 @@ public final class DataSourceBuilder<T extends DataSource> {
* @return this builder
*/
@SuppressWarnings("unchecked")
public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
public <D extends DataSource> DataSourceBuilder<D> type(@Nullable Class<D> type) {
this.type = (Class<T>) type;
return (DataSourceBuilder<D>) this;
}
@@ -147,7 +149,7 @@ public final class DataSourceBuilder<T extends DataSource> {
* @param username the user name
* @return this builder
*/
public DataSourceBuilder<T> username(String username) {
public DataSourceBuilder<T> username(@Nullable String username) {
set(DataSourceProperty.USERNAME, username);
return this;
}
@@ -157,12 +159,12 @@ public final class DataSourceBuilder<T extends DataSource> {
* @param password the password
* @return this builder
*/
public DataSourceBuilder<T> password(String password) {
public DataSourceBuilder<T> password(@Nullable String password) {
set(DataSourceProperty.PASSWORD, password);
return this;
}
private void set(DataSourceProperty property, String value) {
private void set(DataSourceProperty property, @Nullable String value) {
this.values.put(property, value);
}
@@ -178,7 +180,8 @@ public final class DataSourceBuilder<T extends DataSource> {
Set<DataSourceProperty> applied = new HashSet<>();
for (DataSourceProperty property : DataSourceProperty.values()) {
String value = this.values.get(property);
if (value == null && deriveFromProperties != null && properties.canSet(property)) {
if (value == null && deriveFromProperties != null && this.deriveFrom != null
&& properties.canSet(property)) {
value = deriveFromProperties.get(this.deriveFrom, property);
}
if (value != null) {
@@ -193,14 +196,14 @@ public final class DataSourceBuilder<T extends DataSource> {
DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url);
String driverClassName = driver.getDriverClassName();
if (driverClassName != null) {
properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName());
properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driverClassName);
}
}
return dataSource;
}
@SuppressWarnings("unchecked")
private DataSourceProperties<DataSource> getDeriveFromProperties() {
private @Nullable DataSourceProperties<DataSource> getDeriveFromProperties() {
if (this.deriveFrom == null) {
return null;
}
@@ -220,7 +223,7 @@ public final class DataSourceBuilder<T extends DataSource> {
* @param classLoader the classloader used to discover preferred settings
* @return a new {@link DataSource} builder instance
*/
public static DataSourceBuilder<?> create(ClassLoader classLoader) {
public static DataSourceBuilder<?> create(@Nullable ClassLoader classLoader) {
return new DataSourceBuilder<>(classLoader);
}
@@ -258,7 +261,7 @@ public final class DataSourceBuilder<T extends DataSource> {
* @param classLoader the classloader used to discover preferred settings
* @return the preferred {@link DataSource} type
*/
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
public static @Nullable Class<? extends DataSource> findType(@Nullable ClassLoader classLoader) {
MappedDataSourceProperties<?> mappings = MappedDataSourceProperties.forType(classLoader, null);
return (mappings != null) ? mappings.getDataSourceInstanceType() : null;
}
@@ -294,15 +297,15 @@ public final class DataSourceBuilder<T extends DataSource> {
return this.names[0];
}
Method findSetter(Class<?> type) {
@Nullable Method findSetter(Class<?> type) {
return findMethod("set", type, String.class);
}
Method findGetter(Class<?> type) {
@Nullable Method findGetter(Class<?> type) {
return findMethod("get", type);
}
private Method findMethod(String prefix, Class<?> type, Class<?>... paramTypes) {
private @Nullable Method findMethod(String prefix, Class<?> type, Class<?>... paramTypes) {
for (String name : this.names) {
String candidate = prefix + StringUtils.capitalize(name);
Method method = ReflectionUtils.findMethod(type, candidate, paramTypes);
@@ -323,11 +326,16 @@ public final class DataSourceBuilder<T extends DataSource> {
void set(T dataSource, DataSourceProperty property, String value);
String get(T dataSource, DataSourceProperty property);
@Nullable String get(T dataSource, DataSourceProperty property);
static <T extends DataSource> DataSourceProperties<T> forType(ClassLoader classLoader, Class<T> type) {
static <T extends DataSource> DataSourceProperties<T> forType(@Nullable ClassLoader classLoader,
@Nullable Class<T> type) {
MappedDataSourceProperties<T> mapped = MappedDataSourceProperties.forType(classLoader, type);
return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type);
if (mapped != null) {
return mapped;
}
Assert.state(type != null, "No supported DataSource type found");
return new ReflectionDataSourceProperties<>(type);
}
}
@@ -338,10 +346,16 @@ public final class DataSourceBuilder<T extends DataSource> {
private final Class<T> dataSourceType;
@SuppressWarnings("unchecked")
MappedDataSourceProperties() {
this.dataSourceType = (Class<T>) ResolvableType.forClass(MappedDataSourceProperties.class, getClass())
this.dataSourceType = getGeneric();
}
@SuppressWarnings("unchecked")
private Class<T> getGeneric() {
Class<T> generic = (Class<T>) ResolvableType.forClass(MappedDataSourceProperties.class, getClass())
.resolveGeneric();
Assert.state(generic != null, "'generic' must not be null");
return generic;
}
@Override
@@ -349,11 +363,12 @@ public final class DataSourceBuilder<T extends DataSource> {
return this.dataSourceType;
}
protected void add(DataSourceProperty property, Getter<T, String> getter, Setter<T, String> setter) {
protected void add(DataSourceProperty property, @Nullable Getter<T, String> getter, Setter<T, String> setter) {
add(property, String.class, getter, setter);
}
protected <V> void add(DataSourceProperty property, Class<V> type, Getter<T, V> getter, Setter<T, V> setter) {
protected <V> void add(DataSourceProperty property, Class<V> type, @Nullable Getter<T, V> getter,
Setter<T, V> setter) {
this.mappedProperties.put(property, new MappedDataSourceProperty<>(property, type, getter, setter));
}
@@ -371,7 +386,7 @@ public final class DataSourceBuilder<T extends DataSource> {
}
@Override
public String get(T dataSource, DataSourceProperty property) {
public @Nullable String get(T dataSource, DataSourceProperty property) {
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
if (mappedProperty != null) {
return mappedProperty.get(dataSource);
@@ -379,14 +394,15 @@ public final class DataSourceBuilder<T extends DataSource> {
return null;
}
private MappedDataSourceProperty<T, ?> getMapping(DataSourceProperty property) {
private @Nullable MappedDataSourceProperty<T, ?> getMapping(DataSourceProperty property) {
MappedDataSourceProperty<T, ?> mappedProperty = this.mappedProperties.get(property);
UnsupportedDataSourcePropertyException.throwIf(!property.isOptional() && mappedProperty == null,
() -> "No mapping found for " + property);
return mappedProperty;
}
static <T extends DataSource> MappedDataSourceProperties<T> forType(ClassLoader classLoader, Class<T> type) {
static <T extends DataSource> @Nullable MappedDataSourceProperties<T> forType(@Nullable ClassLoader classLoader,
@Nullable Class<T> type) {
MappedDataSourceProperties<T> pooled = lookupPooled(classLoader, type);
if (type == null || pooled != null) {
return pooled;
@@ -394,8 +410,8 @@ public final class DataSourceBuilder<T extends DataSource> {
return lookupBasic(classLoader, type);
}
private static <T extends DataSource> MappedDataSourceProperties<T> lookupPooled(ClassLoader classLoader,
Class<T> type) {
private static <T extends DataSource> @Nullable MappedDataSourceProperties<T> lookupPooled(
@Nullable ClassLoader classLoader, @Nullable Class<T> type) {
MappedDataSourceProperties<T> result = null;
result = lookup(classLoader, type, result, "com.zaxxer.hikari.HikariDataSource",
HikariDataSourceProperties::new);
@@ -412,8 +428,8 @@ public final class DataSourceBuilder<T extends DataSource> {
return result;
}
private static <T extends DataSource> MappedDataSourceProperties<T> lookupBasic(ClassLoader classLoader,
Class<T> dataSourceType) {
private static <T extends DataSource> @Nullable MappedDataSourceProperties<T> lookupBasic(
@Nullable ClassLoader classLoader, Class<T> dataSourceType) {
MappedDataSourceProperties<T> result = null;
result = lookup(classLoader, dataSourceType, result,
"org.springframework.jdbc.datasource.SimpleDriverDataSource", SimpleDataSourceProperties::new);
@@ -427,8 +443,9 @@ public final class DataSourceBuilder<T extends DataSource> {
}
@SuppressWarnings("unchecked")
private static <T extends DataSource> MappedDataSourceProperties<T> lookup(ClassLoader classLoader,
Class<T> dataSourceType, MappedDataSourceProperties<T> existing, String dataSourceClassName,
private static <T extends DataSource> @Nullable MappedDataSourceProperties<T> lookup(
@Nullable ClassLoader classLoader, @Nullable Class<T> dataSourceType,
@Nullable MappedDataSourceProperties<T> existing, String dataSourceClassName,
Supplier<MappedDataSourceProperties<?>> propertyMappingsSupplier, String... requiredClassNames) {
if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) {
return existing;
@@ -439,7 +456,7 @@ public final class DataSourceBuilder<T extends DataSource> {
? (MappedDataSourceProperties<T>) propertyMappings : null;
}
private static boolean allPresent(ClassLoader classLoader, String dataSourceClassName,
private static boolean allPresent(@Nullable ClassLoader classLoader, String dataSourceClassName,
String[] requiredClassNames) {
boolean result = ClassUtils.isPresent(dataSourceClassName, classLoader);
for (String requiredClassName : requiredClassNames) {
@@ -456,11 +473,12 @@ public final class DataSourceBuilder<T extends DataSource> {
private final Class<V> type;
private final Getter<T, V> getter;
private final @Nullable Getter<T, V> getter;
private final Setter<T, V> setter;
private final @Nullable Setter<T, V> setter;
MappedDataSourceProperty(DataSourceProperty property, Class<V> type, Getter<T, V> getter, Setter<T, V> setter) {
MappedDataSourceProperty(DataSourceProperty property, Class<V> type, @Nullable Getter<T, V> getter,
@Nullable Setter<T, V> setter) {
this.property = property;
this.type = type;
this.getter = getter;
@@ -481,7 +499,7 @@ public final class DataSourceBuilder<T extends DataSource> {
}
}
String get(T dataSource) {
@Nullable String get(T dataSource) {
try {
if (this.getter == null) {
UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(),
@@ -506,7 +524,8 @@ public final class DataSourceBuilder<T extends DataSource> {
throw new IllegalStateException("Unsupported value type " + this.type);
}
private String convertToString(V value) {
@Contract("!null -> !null")
private @Nullable String convertToString(@Nullable V value) {
if (value == null) {
return null;
}
@@ -530,7 +549,6 @@ public final class DataSourceBuilder<T extends DataSource> {
private final Class<T> dataSourceType;
ReflectionDataSourceProperties(Class<T> dataSourceType) {
Assert.state(dataSourceType != null, "No supported DataSource type found");
Map<DataSourceProperty, Method> getters = new HashMap<>();
Map<DataSourceProperty, Method> setters = new HashMap<>();
for (DataSourceProperty property : DataSourceProperty.values()) {
@@ -542,7 +560,8 @@ public final class DataSourceBuilder<T extends DataSource> {
this.setters = Collections.unmodifiableMap(setters);
}
private void putIfNotNull(Map<DataSourceProperty, Method> map, DataSourceProperty property, Method method) {
private void putIfNotNull(Map<DataSourceProperty, Method> map, DataSourceProperty property,
@Nullable Method method) {
if (method != null) {
map.put(property, method);
}
@@ -567,7 +586,7 @@ public final class DataSourceBuilder<T extends DataSource> {
}
@Override
public String get(T dataSource, DataSourceProperty property) {
public @Nullable String get(T dataSource, DataSourceProperty property) {
Method method = getMethod(property, this.getters);
if (method != null) {
return (String) ReflectionUtils.invokeMethod(method, dataSource);
@@ -575,7 +594,7 @@ public final class DataSourceBuilder<T extends DataSource> {
return null;
}
private Method getMethod(DataSourceProperty property, Map<DataSourceProperty, Method> methods) {
private @Nullable Method getMethod(DataSourceProperty property, Map<DataSourceProperty, Method> methods) {
Method method = methods.get(property);
if (method == null) {
UnsupportedDataSourcePropertyException.throwIf(!property.isOptional(),
@@ -591,7 +610,7 @@ public final class DataSourceBuilder<T extends DataSource> {
@FunctionalInterface
private interface Getter<T, V> {
V get(T instance) throws SQLException;
@Nullable V get(T instance) throws SQLException;
}
@@ -22,6 +22,8 @@ import java.util.List;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -51,7 +53,7 @@ class DataSourceBuilderRuntimeHints implements RuntimeHintsRegistrar {
}
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
for (String typeName : TYPE_NAMES) {
hints.reflection()
.registerTypeIfPresent(classLoader, typeName,
@@ -20,6 +20,8 @@ import java.sql.Wrapper;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.jdbc.datasource.DelegatingDataSource;
@@ -53,7 +55,8 @@ public final class DataSourceUnwrapper {
* @since 2.3.8
* @see Wrapper#unwrap(Class)
*/
public static <I, T extends I> T unwrap(DataSource dataSource, Class<I> unwrapInterface, Class<T> target) {
public static <I, T extends I> @Nullable T unwrap(DataSource dataSource, Class<I> unwrapInterface,
Class<T> target) {
if (target.isInstance(dataSource)) {
return target.cast(dataSource);
}
@@ -86,11 +89,11 @@ public final class DataSourceUnwrapper {
* @param <T> the target type
* @return an object that implements the target type or {@code null}
*/
public static <T> T unwrap(DataSource dataSource, Class<T> target) {
public static <T> @Nullable T unwrap(DataSource dataSource, Class<T> target) {
return unwrap(dataSource, target, target);
}
private static <S> S safeUnwrap(Wrapper wrapper, Class<S> target) {
private static <S> @Nullable S safeUnwrap(Wrapper wrapper, Class<S> target) {
try {
if (target.isInterface() && wrapper.isWrapperFor(target)) {
return wrapper.unwrap(target);
@@ -104,7 +107,7 @@ public final class DataSourceUnwrapper {
private static final class DelegatingDataSourceUnwrapper {
private static DataSource getTargetDataSource(DataSource dataSource) {
private static @Nullable DataSource getTargetDataSource(DataSource dataSource) {
if (dataSource instanceof DelegatingDataSource delegatingDataSource) {
return delegatingDataSource.getTargetDataSource();
}
@@ -21,6 +21,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -233,23 +235,25 @@ public enum DatabaseDriver {
};
private final String productName;
private final @Nullable String productName;
private final String driverClassName;
private final @Nullable String driverClassName;
private final String xaDataSourceClassName;
private final @Nullable String xaDataSourceClassName;
private final String validationQuery;
private final @Nullable String validationQuery;
DatabaseDriver(String productName, String driverClassName) {
DatabaseDriver(@Nullable String productName, @Nullable String driverClassName) {
this(productName, driverClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName) {
DatabaseDriver(@Nullable String productName, @Nullable String driverClassName,
@Nullable String xaDataSourceClassName) {
this(productName, driverClassName, xaDataSourceClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName, String validationQuery) {
DatabaseDriver(@Nullable String productName, @Nullable String driverClassName,
@Nullable String xaDataSourceClassName, @Nullable String validationQuery) {
this.productName = productName;
this.driverClassName = driverClassName;
this.xaDataSourceClassName = xaDataSourceClassName;
@@ -280,7 +284,7 @@ public enum DatabaseDriver {
* Return the driver class name.
* @return the class name or {@code null}
*/
public String getDriverClassName() {
public @Nullable String getDriverClassName() {
return this.driverClassName;
}
@@ -288,7 +292,7 @@ public enum DatabaseDriver {
* Return the XA driver source class name.
* @return the class name or {@code null}
*/
public String getXaDataSourceClassName() {
public @Nullable String getXaDataSourceClassName() {
return this.xaDataSourceClassName;
}
@@ -296,7 +300,7 @@ public enum DatabaseDriver {
* Return the validation query.
* @return the validation query or {@code null}
*/
public String getValidationQuery() {
public @Nullable String getValidationQuery() {
return this.validationQuery;
}
@@ -305,7 +309,7 @@ public enum DatabaseDriver {
* @param url the JDBC URL
* @return the database driver or {@link #UNKNOWN} if not found
*/
public static DatabaseDriver fromJdbcUrl(String url) {
public static DatabaseDriver fromJdbcUrl(@Nullable String url) {
if (StringUtils.hasLength(url)) {
Assert.isTrue(url.startsWith("jdbc"), "'url' must start with \"jdbc\"");
String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH);
@@ -326,7 +330,7 @@ public enum DatabaseDriver {
* @param productName product name
* @return the database driver or {@link #UNKNOWN} if not found
*/
public static DatabaseDriver fromProductName(String productName) {
public static DatabaseDriver fromProductName(@Nullable String productName) {
if (StringUtils.hasLength(productName)) {
for (DatabaseDriver candidate : values()) {
if (candidate.matchProductName(productName)) {
@@ -24,6 +24,8 @@ import java.util.stream.Stream;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.Assert;
@@ -64,15 +66,15 @@ public enum EmbeddedDatabaseConnection {
*/
HSQLDB("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s");
private final String alternativeDriverClass;
private final @Nullable String alternativeDriverClass;
private final String url;
private final @Nullable String url;
EmbeddedDatabaseConnection(String url) {
EmbeddedDatabaseConnection(@Nullable String url) {
this(null, url);
}
EmbeddedDatabaseConnection(String fallbackDriverClass, String url) {
EmbeddedDatabaseConnection(@Nullable String fallbackDriverClass, @Nullable String url) {
this.alternativeDriverClass = fallbackDriverClass;
this.url = url;
}
@@ -81,7 +83,7 @@ public enum EmbeddedDatabaseConnection {
* Returns the driver class name.
* @return the driver class name
*/
public String getDriverClassName() {
public @Nullable String getDriverClassName() {
// See https://github.com/spring-projects/spring-boot/issues/32865
return switch (this) {
case NONE -> null;
@@ -95,7 +97,7 @@ public enum EmbeddedDatabaseConnection {
* Returns the {@link EmbeddedDatabaseType} for the connection.
* @return the database type
*/
public EmbeddedDatabaseType getType() {
public @Nullable EmbeddedDatabaseType getType() {
// See https://github.com/spring-projects/spring-boot/issues/32865
return switch (this) {
case NONE -> null;
@@ -110,7 +112,7 @@ public enum EmbeddedDatabaseConnection {
* @param databaseName the name of the database
* @return the connection URL
*/
public String getUrl(String databaseName) {
public @Nullable String getUrl(String databaseName) {
Assert.hasText(databaseName, "'databaseName' must not be empty");
return (this.url != null) ? String.format(this.url, databaseName) : null;
}
@@ -125,7 +127,7 @@ public enum EmbeddedDatabaseConnection {
};
}
boolean isDriverCompatible(String driverClass) {
boolean isDriverCompatible(@Nullable String driverClass) {
return (driverClass != null
&& (driverClass.equals(getDriverClassName()) || driverClass.equals(this.alternativeDriverClass)));
}
@@ -138,7 +140,7 @@ public enum EmbeddedDatabaseConnection {
* @return true if the driver class and url refer to an embedded database
* @since 2.4.0
*/
public static boolean isEmbedded(String driverClass, String url) {
public static boolean isEmbedded(@Nullable String driverClass, @Nullable String url) {
if (driverClass == null) {
return false;
}
@@ -178,9 +180,14 @@ public enum EmbeddedDatabaseConnection {
* @param classLoader the class loader used to check for classes
* @return an {@link EmbeddedDatabaseConnection} or {@link #NONE}.
*/
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
public static EmbeddedDatabaseConnection get(@Nullable ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) {
if (candidate == NONE) {
continue;
}
String driverClassName = candidate.getDriverClassName();
Assert.state(driverClassName != null, "'driverClassName' must not be null");
if (ClassUtils.isPresent(driverClassName, classLoader)) {
return candidate;
}
}
@@ -202,7 +209,12 @@ public enum EmbeddedDatabaseConnection {
productName = productName.toUpperCase(Locale.ENGLISH);
EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values();
for (EmbeddedDatabaseConnection candidate : candidates) {
if (candidate != NONE && productName.contains(candidate.getType().name())) {
if (candidate == NONE) {
continue;
}
EmbeddedDatabaseType type = candidate.getType();
Assert.state(type != null, "'type' must not be null");
if (productName.contains(type.name())) {
String url = metaData.getURL();
return (url == null || candidate.isEmbeddedUrl(url));
}
@@ -33,6 +33,7 @@ import com.zaxxer.hikari.HikariPoolMXBean;
import com.zaxxer.hikari.pool.HikariPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.Lifecycle;
@@ -71,7 +72,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
private final Function<HikariPool, Boolean> hasOpenConnections;
private final HikariDataSource dataSource;
private final @Nullable HikariDataSource dataSource;
private final ConfigurableApplicationContext applicationContext;
@@ -123,15 +124,16 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
+ "Please configure allow-pool-suspension to fix this!");
}
}
closeConnections(Duration.ofMillis(this.dataSource.getConnectionTimeout() + 250));
closeConnections(this.dataSource, Duration.ofMillis(this.dataSource.getConnectionTimeout() + 250));
}
private void closeConnections(Duration shutdownTimeout) {
private void closeConnections(HikariDataSource dataSource, Duration shutdownTimeout) {
logger.info("Evicting Hikari connections");
this.dataSource.getHikariPoolMXBean().softEvictConnections();
dataSource.getHikariPoolMXBean().softEvictConnections();
logger.debug(LogMessage.format("Waiting %d seconds for Hikari connections to be closed",
shutdownTimeout.toSeconds()));
CompletableFuture<Void> allConnectionsClosed = CompletableFuture.runAsync(this::waitForConnectionsToClose);
CompletableFuture<Void> allConnectionsClosed = CompletableFuture
.runAsync(() -> this.waitForConnectionsToClose(dataSource));
try {
allConnectionsClosed.get(shutdownTimeout.toMillis(), TimeUnit.MILLISECONDS);
logger.debug("Hikari connections closed");
@@ -148,8 +150,8 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle {
}
}
private void waitForConnectionsToClose() {
while (this.hasOpenConnections.apply((HikariPool) this.dataSource.getHikariPoolMXBean())) {
private void waitForConnectionsToClose(HikariDataSource dataSource) {
while (this.hasOpenConnections.apply((HikariPool) dataSource.getHikariPoolMXBean())) {
try {
TimeUnit.MILLISECONDS.sleep(50);
}
@@ -23,6 +23,7 @@ import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -49,14 +50,14 @@ import org.springframework.util.StringUtils;
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader) {
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails,
@Nullable Class<? extends DataSource> type, ClassLoader classLoader) {
return createDataSource(connectionDetails, type, classLoader, true);
}
@SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader, boolean applyDriverClassName) {
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails,
@Nullable Class<? extends DataSource> type, ClassLoader classLoader, boolean applyDriverClassName) {
DataSourceBuilder<? extends DataSource> builder = DataSourceBuilder.create(classLoader).type(type);
if (applyDriverClassName) {
builder.driverClassName(connectionDetails.getDriverClassName());
@@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -57,7 +59,8 @@ public final class DataSourceInitializationAutoConfiguration {
determineDataSource(dataSource, properties.getUsername(), properties.getPassword()), properties);
}
private static DataSource determineDataSource(DataSource dataSource, String username, String password) {
private static DataSource determineDataSource(DataSource dataSource, @Nullable String username,
@Nullable String password) {
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
return DataSourceBuilder.derivedFrom(dataSource)
.username(username)
@@ -26,6 +26,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
@@ -81,7 +82,7 @@ class DataSourceJmxConfiguration {
@Bean
@ConditionalOnMissingBean(name = "dataSourceMBean")
Object dataSourceMBean(DataSource dataSource) {
@Nullable Object dataSourceMBean(DataSource dataSource) {
DataSourceProxy dataSourceProxy = DataSourceUnwrapper.unwrap(dataSource, PoolConfiguration.class,
DataSourceProxy.class);
if (dataSourceProxy != null) {
@@ -23,6 +23,7 @@ import oracle.ucp.jdbc.PoolDataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceMXBean;
import org.apache.tomcat.jdbc.pool.jmx.ConnectionPoolMBean;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -123,7 +124,7 @@ public class DataSourcePoolMetadataProvidersConfiguration {
static class HikariDataSourcePoolMetadataRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(HikariDataSource.class, (builder) -> builder.withField("pool"));
}
@@ -22,6 +22,8 @@ import java.util.UUID;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
@@ -47,6 +49,7 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties("spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
@SuppressWarnings("NullAway.Init")
private ClassLoader classLoader;
/**
@@ -58,49 +61,50 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* Datasource name to use if "generate-unique-name" is false. Defaults to "testdb"
* when using an embedded database, otherwise null.
*/
private String name;
private @Nullable String name;
/**
* Fully qualified name of the DataSource implementation to use. By default, a
* connection pool implementation is auto-detected from the classpath.
*/
private Class<? extends DataSource> type;
private @Nullable Class<? extends DataSource> type;
/**
* Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
*/
private String driverClassName;
private @Nullable String driverClassName;
/**
* JDBC URL of the database.
*/
private String url;
private @Nullable String url;
/**
* Login username of the database.
*/
private String username;
private @Nullable String username;
/**
* Login password of the database.
*/
private String password;
private @Nullable String password;
/**
* JNDI location of the datasource. Class, url, username and password are ignored when
* set.
*/
private String jndiName;
private @Nullable String jndiName;
/**
* Connection details for an embedded database. Defaults to the most suitable embedded
* database that is available on the classpath.
*/
@SuppressWarnings("NullAway.Init")
private EmbeddedDatabaseConnection embeddedDatabaseConnection;
private Xa xa = new Xa();
private String uniqueName;
private @Nullable String uniqueName;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
@@ -136,19 +140,19 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
this.generateUniqueName = generateUniqueName;
}
public String getName() {
public @Nullable String getName() {
return this.name;
}
public void setName(String name) {
public void setName(@Nullable String name) {
this.name = name;
}
public Class<? extends DataSource> getType() {
public @Nullable Class<? extends DataSource> getType() {
return this.type;
}
public void setType(Class<? extends DataSource> type) {
public void setType(@Nullable Class<? extends DataSource> type) {
this.type = type;
}
@@ -157,11 +161,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* @return the configured driver
* @see #determineDriverClassName()
*/
public String getDriverClassName() {
public @Nullable String getDriverClassName() {
return this.driverClassName;
}
public void setDriverClassName(String driverClassName) {
public void setDriverClassName(@Nullable String driverClassName) {
this.driverClassName = driverClassName;
}
@@ -178,9 +182,10 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
return driverClassName;
}
String findDriverClassName() {
@Nullable String findDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
Assert.state(driverClassIsLoadable(this.driverClassName),
() -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
@@ -193,9 +198,9 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
return driverClassName;
}
private boolean driverClassIsLoadable() {
private boolean driverClassIsLoadable(String driverClassName) {
try {
ClassUtils.forName(this.driverClassName, null);
ClassUtils.forName(driverClassName, null);
return true;
}
catch (UnsupportedClassVersionError ex) {
@@ -212,11 +217,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* @return the configured url
* @see #determineUrl()
*/
public String getUrl() {
public @Nullable String getUrl() {
return this.url;
}
public void setUrl(String url) {
public void setUrl(@Nullable String url) {
this.url = url;
}
@@ -241,7 +246,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* Determine the name to used based on this configuration.
* @return the database name to use or {@code null}
*/
public String determineDatabaseName() {
public @Nullable String determineDatabaseName() {
if (this.generateUniqueName) {
if (this.uniqueName == null) {
this.uniqueName = UUID.randomUUID().toString();
@@ -262,11 +267,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* @return the configured username
* @see #determineUsername()
*/
public String getUsername() {
public @Nullable String getUsername() {
return this.username;
}
public void setUsername(String username) {
public void setUsername(@Nullable String username) {
this.username = username;
}
@@ -274,7 +279,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* Determine the username to use based on this configuration and the environment.
* @return the username to use
*/
public String determineUsername() {
public @Nullable String determineUsername() {
if (StringUtils.hasText(this.username)) {
return this.username;
}
@@ -289,11 +294,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* @return the configured password
* @see #determinePassword()
*/
public String getPassword() {
public @Nullable String getPassword() {
return this.password;
}
public void setPassword(String password) {
public void setPassword(@Nullable String password) {
this.password = password;
}
@@ -301,7 +306,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* Determine the password to use based on this configuration and the environment.
* @return the password to use
*/
public String determinePassword() {
public @Nullable String determinePassword() {
if (StringUtils.hasText(this.password)) {
return this.password;
}
@@ -311,7 +316,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
return null;
}
public String getJndiName() {
public @Nullable String getJndiName() {
return this.jndiName;
}
@@ -321,7 +326,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* will be ignored when using JNDI lookups.
* @param jndiName the JNDI name
*/
public void setJndiName(String jndiName) {
public void setJndiName(@Nullable String jndiName) {
this.jndiName = jndiName;
}
@@ -353,18 +358,18 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
/**
* XA datasource fully qualified name.
*/
private String dataSourceClassName;
private @Nullable String dataSourceClassName;
/**
* Properties to pass to the XA data source.
*/
private Map<String, String> properties = new LinkedHashMap<>();
public String getDataSourceClassName() {
public @Nullable String getDataSourceClassName() {
return this.dataSourceClassName;
}
public void setDataSourceClassName(String dataSourceClassName) {
public void setDataSourceClassName(@Nullable String dataSourceClassName) {
this.dataSourceClassName = dataSourceClassName;
}
@@ -23,6 +23,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.Assert;
/**
* Configuration for embedded data sources.
@@ -36,6 +38,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@EnableConfigurationProperties(DataSourceProperties.class)
public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private ClassLoader classLoader;
@Override
@@ -45,9 +48,11 @@ public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
@Bean(destroyMethod = "shutdown")
public EmbeddedDatabase dataSource(DataSourceProperties properties) {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.get(this.classLoader).getType())
.setName(properties.determineDatabaseName())
.build();
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(this.classLoader).getType();
String databaseName = properties.determineDatabaseName();
Assert.state(type != null, "'type' must not be null");
Assert.state(databaseName != null, "'databaseName' must not be null");
return new EmbeddedDatabaseBuilder().setType(type).setName(databaseName).build();
}
}
@@ -16,6 +16,8 @@
package org.springframework.boot.jdbc.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
@@ -31,7 +33,7 @@ class HikariDriverConfigurationFailureAnalyzer extends AbstractFailureAnalyzer<C
private static final String EXPECTED_MESSAGE = "cannot use driverClassName and dataSourceClassName together.";
@Override
protected FailureAnalysis analyze(Throwable rootFailure, CannotGetJdbcConnectionException cause) {
protected @Nullable FailureAnalysis analyze(Throwable rootFailure, CannotGetJdbcConnectionException cause) {
Throwable subCause = cause.getCause();
if (subCause == null || !EXPECTED_MESSAGE.equals(subCause.getMessage())) {
return null;
@@ -16,8 +16,11 @@
package org.springframework.boot.jdbc.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.util.Assert;
/**
* Details required to establish a connection to an SQL service using JDBC.
@@ -33,13 +36,13 @@ public interface JdbcConnectionDetails extends ConnectionDetails {
* Username for the database.
* @return the username for the database
*/
String getUsername();
@Nullable String getUsername();
/**
* Password for the database.
* @return the password for the database
*/
String getPassword();
@Nullable String getPassword();
/**
* JDBC url for the database.
@@ -56,7 +59,9 @@ public interface JdbcConnectionDetails extends ConnectionDetails {
* @see DatabaseDriver#getDriverClassName()
*/
default String getDriverClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
String driverClassName = DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
Assert.state(driverClassName != null, "'driverClassName' must not be null");
return driverClassName;
}
/**
@@ -67,7 +72,7 @@ public interface JdbcConnectionDetails extends ConnectionDetails {
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getXaDataSourceClassName()
*/
default String getXaDataSourceClassName() {
default @Nullable String getXaDataSourceClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getXaDataSourceClassName();
}
@@ -19,6 +19,8 @@ package org.springframework.boot.jdbc.autoconfigure;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@@ -65,7 +67,7 @@ public class JdbcProperties {
* duration suffix is not specified, seconds will be used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration queryTimeout;
private @Nullable Duration queryTimeout;
/**
* Whether results processing should be skipped. Can be used to optimize callable
@@ -108,11 +110,11 @@ public class JdbcProperties {
this.maxRows = maxRows;
}
public Duration getQueryTimeout() {
public @Nullable Duration getQueryTimeout() {
return this.queryTimeout;
}
public void setQueryTimeout(Duration queryTimeout) {
public void setQueryTimeout(@Nullable Duration queryTimeout) {
this.queryTimeout = queryTimeout;
}
@@ -30,6 +30,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.JmxUtils;
import org.springframework.util.Assert;
/**
* {@link EnableAutoConfiguration Auto-configuration} for a JNDI located
@@ -49,7 +50,9 @@ public final class JndiDataSourceAutoConfiguration {
@ConditionalOnMissingBean
DataSource dataSource(DataSourceProperties properties, ApplicationContext context) {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource(properties.getJndiName());
String jndiName = properties.getJndiName();
Assert.state(jndiName != null, "'jndiName' must not be null");
DataSource dataSource = dataSourceLookup.getDataSource(jndiName);
excludeMBeanIfNecessary(dataSource, "dataSource", context);
return dataSource;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.jdbc.autoconfigure;
import org.jspecify.annotations.Nullable;
/**
* Adapts {@link DataSourceProperties} to {@link JdbcConnectionDetails}.
*
@@ -30,12 +32,12 @@ final class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
}
@Override
public String getUsername() {
public @Nullable String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
public @Nullable String getPassword() {
return this.properties.determinePassword();
}
@@ -50,7 +52,7 @@ final class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
}
@Override
public String getXaDataSourceClassName() {
public @Nullable String getXaDataSourceClassName() {
return (this.properties.getXa().getDataSourceClassName() != null)
? this.properties.getXa().getDataSourceClassName()
: JdbcConnectionDetails.super.getXaDataSourceClassName();
@@ -65,6 +65,7 @@ import org.springframework.util.StringUtils;
@ConditionalOnMissingBean(DataSource.class)
public final class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
@SuppressWarnings("NullAway.Init")
private ClassLoader classLoader;
@Bean
@@ -26,6 +26,8 @@ import java.util.stream.Stream;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -72,6 +74,7 @@ public final class DataSourceHealthContributorAutoConfiguration implements Initi
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
@SuppressWarnings("NullAway.Init")
private DataSourcePoolMetadataProvider poolMetadataProvider;
DataSourceHealthContributorAutoConfiguration(ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
@@ -114,7 +117,7 @@ public final class DataSourceHealthContributorAutoConfiguration implements Initi
return new DataSourceHealthIndicator(source, getValidationQuery(source));
}
private String getValidationQuery(DataSource source) {
private @Nullable String getValidationQuery(DataSource source) {
DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider.getDataSourcePoolMetadata(source);
return (poolMetadata != null) ? poolMetadata.getValidationQuery() : null;
}
@@ -165,7 +168,7 @@ public final class DataSourceHealthContributorAutoConfiguration implements Initi
}
@Override
public HealthContributor getContributor(String name) {
public @Nullable HealthContributor getContributor(String name) {
return this.delegate.getContributor(name);
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for JDBC health.
*/
@NullMarked
package org.springframework.boot.jdbc.autoconfigure.health;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for JDBC metrics.
*/
@NullMarked
package org.springframework.boot.jdbc.autoconfigure.metrics;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for JDBC.
*/
@NullMarked
package org.springframework.boot.jdbc.autoconfigure;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.jdbc.docker.compose;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -61,11 +63,11 @@ class JdbcUrlBuilder {
* @param database the database to connect to
* @return a new JDBC URL
*/
String build(RunningService service, String database) {
String build(RunningService service, @Nullable String database) {
return urlFor(service, database);
}
private String urlFor(RunningService service, String database) {
private String urlFor(RunningService service, @Nullable String database) {
Assert.notNull(service, "'service' must not be null");
StringBuilder url = new StringBuilder("jdbc:%s://%s:%d".formatted(this.driverProtocol, service.host(),
service.ports().get(this.containerPort)));
@@ -91,7 +93,7 @@ class JdbcUrlBuilder {
url.append("?").append(parameters);
}
private String getParameters(RunningService service) {
private @Nullable String getParameters(RunningService service) {
return service.labels().get(PARAMETERS_LABEL);
}
@@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.docker.compose;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -43,7 +45,7 @@ class PostgresEnvironment {
private final String username;
private final String password;
private final @Nullable String password;
private final String database;
@@ -62,7 +64,7 @@ class PostgresEnvironment {
return defaultValue;
}
private String extractPassword(Map<String, String> env) {
private @Nullable String extractPassword(Map<String, String> env) {
if (isUsingTrustHostAuthMethod(env)) {
return null;
}
@@ -81,7 +83,7 @@ class PostgresEnvironment {
return this.username;
}
String getPassword() {
@Nullable String getPassword() {
return this.password;
}
@@ -19,6 +19,8 @@ package org.springframework.boot.jdbc.docker.compose;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
@@ -74,7 +76,7 @@ class PostgresJdbcDockerComposeConnectionDetailsFactory
}
@Override
public String getPassword() {
public @Nullable String getPassword() {
return this.environment.getPassword();
}
@@ -17,4 +17,7 @@
/**
* Support for Docker Compose JDBC service connections.
*/
@NullMarked
package org.springframework.boot.jdbc.docker.compose;
import org.jspecify.annotations.NullMarked;
@@ -24,6 +24,8 @@ import java.util.List;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
@@ -51,11 +53,11 @@ import org.springframework.util.StringUtils;
*/
public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean {
private DataSource dataSource;
private @Nullable DataSource dataSource;
private String query;
private @Nullable String query;
private JdbcTemplate jdbcTemplate;
private @Nullable JdbcTemplate jdbcTemplate;
/**
* Create a new {@link DataSourceHealthIndicator} instance.
@@ -69,7 +71,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
* {@link DataSource}.
* @param dataSource the data source
*/
public DataSourceHealthIndicator(DataSource dataSource) {
public DataSourceHealthIndicator(@Nullable DataSource dataSource) {
this(dataSource, null);
}
@@ -79,7 +81,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
* @param dataSource the data source
* @param query the validation query to use (can be {@code null})
*/
public DataSourceHealthIndicator(DataSource dataSource, String query) {
public DataSourceHealthIndicator(@Nullable DataSource dataSource, @Nullable String query) {
super("DataSource health check failed");
this.dataSource = dataSource;
this.query = query;
@@ -102,7 +104,8 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
}
private void doDataSourceHealthCheck(Health.Builder builder) {
builder.up().withDetail("database", getProduct());
Assert.state(this.jdbcTemplate != null, "'jdbcTemplate' must not be null");
builder.up().withDetail("database", getProduct(this.jdbcTemplate));
String validationQuery = this.query;
if (StringUtils.hasText(validationQuery)) {
builder.withDetail("validationQuery", validationQuery);
@@ -113,21 +116,21 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
}
else {
builder.withDetail("validationQuery", "isValid()");
boolean valid = isConnectionValid();
boolean valid = isConnectionValid(this.jdbcTemplate);
builder.status((valid) ? Status.UP : Status.DOWN);
}
}
private String getProduct() {
return this.jdbcTemplate.execute((ConnectionCallback<String>) this::getProduct);
private String getProduct(JdbcTemplate jdbcTemplate) {
return jdbcTemplate.execute((ConnectionCallback<String>) this::getProduct);
}
private String getProduct(Connection connection) throws SQLException {
return connection.getMetaData().getDatabaseProductName();
}
private Boolean isConnectionValid() {
return this.jdbcTemplate.execute((ConnectionCallback<Boolean>) this::isConnectionValid);
private Boolean isConnectionValid(JdbcTemplate jdbcTemplate) {
return jdbcTemplate.execute((ConnectionCallback<Boolean>) this::isConnectionValid);
}
private Boolean isConnectionValid(Connection connection) throws SQLException {
@@ -156,7 +159,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
* Return the validation query or {@code null}.
* @return the query
*/
public String getQuery() {
public @Nullable String getQuery() {
return this.query;
}
@@ -172,6 +175,12 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement
if (columns != 1) {
throw new IncorrectResultSetColumnCountException(1, columns);
}
return getResultSetValue(rs);
}
// RowMapper.mapRow isn't defined as @Nullable return type
@SuppressWarnings("NullAway")
private Object getResultSetValue(ResultSet rs) throws SQLException {
return JdbcUtils.getResultSetValue(rs, 1);
}
@@ -17,4 +17,7 @@
/**
* Health integration for JDBC.
*/
@NullMarked
package org.springframework.boot.jdbc.health;
import org.jspecify.annotations.NullMarked;
@@ -18,4 +18,7 @@
* Support for initialization of an SQL database using a JDBC {@link javax.sql.DataSource
* DataSource}.
*/
@NullMarked
package org.springframework.boot.jdbc.init;
import org.jspecify.annotations.NullMarked;
@@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
/**
* A base {@link DataSourcePoolMetadata} implementation.
*
@@ -38,7 +40,7 @@ public abstract class AbstractDataSourcePoolMetadata<T extends DataSource> imple
}
@Override
public Float getUsage() {
public @Nullable Float getUsage() {
Integer maxSize = getMax();
Integer currentSize = getActive();
if (maxSize == null || currentSize == null) {
@@ -22,6 +22,8 @@ import java.util.List;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
/**
* A {@link DataSourcePoolMetadataProvider} implementation that returns the first
* {@link DataSourcePoolMetadata} that is found by one of its delegate.
@@ -38,12 +40,13 @@ public class CompositeDataSourcePoolMetadataProvider implements DataSourcePoolMe
* collection of delegates to use.
* @param providers the data source pool metadata providers
*/
public CompositeDataSourcePoolMetadataProvider(Collection<? extends DataSourcePoolMetadataProvider> providers) {
public CompositeDataSourcePoolMetadataProvider(
@Nullable Collection<? extends DataSourcePoolMetadataProvider> providers) {
this.providers = (providers != null) ? List.copyOf(providers) : Collections.emptyList();
}
@Override
public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
public @Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
for (DataSourcePoolMetadataProvider provider : this.providers) {
DataSourcePoolMetadata metadata = provider.getDataSourcePoolMetadata(dataSource);
if (metadata != null) {
@@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
/**
* Provides access meta-data that is commonly available from most pooled
* {@link DataSource} implementations.
@@ -41,14 +43,14 @@ public interface DataSourcePoolMetadata {
* information to compute the poll usage.
* @return the usage value or {@code null}
*/
Float getUsage();
@Nullable Float getUsage();
/**
* Return the current number of active connections that have been allocated from the
* data source or {@code null} if that information is not available.
* @return the number of active connections or {@code null}
*/
Integer getActive();
@Nullable Integer getActive();
/**
* Return the number of established but idle connections. Can also return {@code null}
@@ -57,7 +59,7 @@ public interface DataSourcePoolMetadata {
* @since 2.2.0
* @see #getActive()
*/
default Integer getIdle() {
default @Nullable Integer getIdle() {
return null;
}
@@ -67,21 +69,21 @@ public interface DataSourcePoolMetadata {
* information is not available.
* @return the maximum number of active connections or {@code null}
*/
Integer getMax();
@Nullable Integer getMax();
/**
* Return the minimum number of idle connections in the pool or {@code null} if that
* information is not available.
* @return the minimum number of active connections or {@code null}
*/
Integer getMin();
@Nullable Integer getMin();
/**
* Return the query to use to validate that a connection is valid or {@code null} if
* that information is not available.
* @return the validation query or {@code null}
*/
String getValidationQuery();
@Nullable String getValidationQuery();
/**
* The default auto-commit state of connections created by this pool. If not set
@@ -89,6 +91,6 @@ public interface DataSourcePoolMetadata {
* java.sql.Connection.setAutoCommit(boolean) method will not be called.)
* @return the default auto-commit state or {@code null}
*/
Boolean getDefaultAutoCommit();
@Nullable Boolean getDefaultAutoCommit();
}
@@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
/**
* Provide a {@link DataSourcePoolMetadata} based on a {@link DataSource}.
*
@@ -33,6 +35,6 @@ public interface DataSourcePoolMetadataProvider {
* @param dataSource the data source
* @return the data source pool metadata
*/
DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource);
@Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource);
}
@@ -20,6 +20,7 @@ import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.DirectFieldAccessor;
@@ -36,9 +37,10 @@ public class HikariDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata
}
@Override
public Integer getActive() {
public @Nullable Integer getActive() {
try {
return getHikariPool().getActiveConnections();
HikariPool hikariPool = getHikariPool();
return (hikariPool != null) ? hikariPool.getActiveConnections() : null;
}
catch (Exception ex) {
return null;
@@ -46,16 +48,17 @@ public class HikariDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata
}
@Override
public Integer getIdle() {
public @Nullable Integer getIdle() {
try {
return getHikariPool().getIdleConnections();
HikariPool hikariPool = getHikariPool();
return (hikariPool != null) ? hikariPool.getIdleConnections() : null;
}
catch (Exception ex) {
return null;
}
}
private HikariPool getHikariPool() {
private @Nullable HikariPool getHikariPool() {
return (HikariPool) new DirectFieldAccessor(getDataSource()).getPropertyValue("pool");
}
@@ -21,6 +21,7 @@ import java.sql.SQLException;
import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils;
@@ -37,7 +38,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad
}
@Override
public Integer getActive() {
public @Nullable Integer getActive() {
try {
return getDataSource().getBorrowedConnectionsCount();
}
@@ -47,7 +48,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad
}
@Override
public Integer getIdle() {
public @Nullable Integer getIdle() {
try {
return getDataSource().getAvailableConnectionsCount();
}
@@ -72,7 +73,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad
}
@Override
public Boolean getDefaultAutoCommit() {
public @Nullable Boolean getDefaultAutoCommit() {
String autoCommit = getDataSource().getConnectionProperty("autoCommit");
return StringUtils.hasText(autoCommit) ? Boolean.valueOf(autoCommit) : null;
}
@@ -17,4 +17,7 @@
/**
* Support for accessing JDBC {@link javax.sql.DataSource} metadata.
*/
@NullMarked
package org.springframework.boot.jdbc.metadata;
import org.jspecify.annotations.NullMarked;
@@ -27,6 +27,7 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.jdbc.metadata.CompositeDataSourcePoolMetadataProvider;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata;
@@ -64,6 +65,7 @@ public class DataSourcePoolMetrics implements MeterBinder {
}
@Override
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
public void bindTo(MeterRegistry registry) {
if (this.metadataProvider.getDataSourcePoolMetadata(this.dataSource) != null) {
bindPoolMetadata(registry, "active",
@@ -79,18 +81,20 @@ public class DataSourcePoolMetrics implements MeterBinder {
}
}
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
private <N extends Number> void bindPoolMetadata(MeterRegistry registry, String metricName, String description,
Function<DataSourcePoolMetadata, N> function) {
Function<DataSourcePoolMetadata, @Nullable N> function) {
bindDataSource(registry, metricName, description, this.metadataProvider.getValueFunction(function));
}
private <N extends Number> void bindDataSource(MeterRegistry registry, String metricName, String description,
Function<DataSource, N> function) {
Function<DataSource, @Nullable N> function) {
if (function.apply(this.dataSource) != null) {
Gauge.builder("jdbc.connections." + metricName, this.dataSource, (m) -> function.apply(m).doubleValue())
.tags(this.tags)
.description(description)
.register(registry);
Gauge.builder("jdbc.connections." + metricName, this.dataSource, (m) -> {
Number value = function.apply(m);
Assert.state(value != null, "'value' must not be null");
return value.doubleValue();
}).tags(this.tags).description(description).register(registry);
}
}
@@ -104,12 +108,17 @@ public class DataSourcePoolMetrics implements MeterBinder {
this.metadataProvider = metadataProvider;
}
<N extends Number> Function<DataSource, N> getValueFunction(Function<DataSourcePoolMetadata, N> function) {
return (dataSource) -> function.apply(getDataSourcePoolMetadata(dataSource));
<N extends Number> Function<DataSource, @Nullable N> getValueFunction(
Function<DataSourcePoolMetadata, @Nullable N> function) {
return (dataSource) -> {
DataSourcePoolMetadata dataSourcePoolMetadata = getDataSourcePoolMetadata(dataSource);
Assert.state(dataSourcePoolMetadata != null, "'dataSourcePoolMetadata' must not be null");
return function.apply(dataSourcePoolMetadata);
};
}
@Override
public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
public @Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
return cache.computeIfAbsent(dataSource,
(key) -> this.metadataProvider.getDataSourcePoolMetadata(dataSource));
}
@@ -17,4 +17,7 @@
/**
* Metrics for JDBC.
*/
@NullMarked
package org.springframework.boot.jdbc.metrics;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Support for Java Database Connectivity (JDBC).
*/
@NullMarked
package org.springframework.boot.jdbc;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Support for testcontainers JDBC service connections.
*/
@NullMarked
package org.springframework.boot.jdbc.testcontainers;
import org.jspecify.annotations.NullMarked;