mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-25 19:09:03 +00:00
Add nullability annotations to module/spring-boot-jdbc
See gh-46587
This commit is contained in:
+62
-43
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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,
|
||||
|
||||
+7
-4
@@ -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();
|
||||
}
|
||||
|
||||
+16
-12
@@ -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
-12
@@ -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));
|
||||
}
|
||||
|
||||
+9
-7
@@ -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);
|
||||
}
|
||||
|
||||
+5
-4
@@ -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());
|
||||
|
||||
+4
-1
@@ -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)
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+2
-1
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
+37
-32
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+9
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -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;
|
||||
}
|
||||
|
||||
+5
-3
@@ -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();
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+5
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Auto-configuration for JDBC health.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.autoconfigure.health;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Auto-configuration for JDBC metrics.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.autoconfigure.metrics;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Auto-configuration for JDBC.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.autoconfigure;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
+5
-3
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Support for Docker Compose JDBC service connections.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.docker.compose;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
+21
-12
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Health integration for JDBC.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.health;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
+3
-1
@@ -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) {
|
||||
|
||||
+5
-2
@@ -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) {
|
||||
|
||||
+9
-7
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+8
-5
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
+18
-9
@@ -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));
|
||||
}
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
+3
@@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Support for testcontainers JDBC service connections.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.boot.jdbc.testcontainers;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
Reference in New Issue
Block a user