Create spring-boot-jdbc module

This commit is contained in:
Andy Wilkinson
2025-03-18 11:11:17 +00:00
committed by Phillip Webb
parent 5360ef8321
commit 8a1a5160c3
223 changed files with 644 additions and 711 deletions

View File

@@ -0,0 +1,761 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.beans.PropertyVetoException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.datasource.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.h2.jdbcx.JdbcDataSource;
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.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Convenience class for building a {@link DataSource}. Provides a limited subset of the
* properties supported by a typical {@link DataSource} as well as detection logic to pick
* the most suitable pooling {@link DataSource} implementation.
* <p>
* The following pooling {@link DataSource} implementations are supported by this builder.
* When no {@link #type(Class) type} has been explicitly set, the first available pool
* implementation will be picked:
* <ul>
* <li>Hikari ({@code com.zaxxer.hikari.HikariDataSource})</li>
* <li>Tomcat JDBC Pool ({@code org.apache.tomcat.jdbc.pool.DataSource})</li>
* <li>Apache DBCP2 ({@code org.apache.commons.dbcp2.BasicDataSource})</li>
* <li>Oracle UCP ({@code oracle.ucp.jdbc.PoolDataSourceImpl})</li>
* <li>C3P0 ({@code com.mchange.v2.c3p0.ComboPooledDataSource})</li>
* <li>Vibur ({@code org.vibur.dbcp.ViburDBCPDataSource})</li>
* </ul>
* <p>
* The following non-pooling {@link DataSource} implementations can be used when
* explicitly set as a {@link #type(Class) type}:
* <ul>
* <li>Spring's {@code SimpleDriverDataSource}
* ({@code org.springframework.jdbc.datasource.SimpleDriverDataSource})</li>
* <li>Oracle ({@code oracle.jdbc.datasource.OracleDataSource})</li>
* <li>H2 ({@code org.h2.jdbcx.JdbcDataSource})</li>
* <li>Postgres ({@code org.postgresql.ds.PGSimpleDataSource})</li>
* <li>Any {@code DataSource} implementation with appropriately named methods</li>
* </ul>
* <p>
* This class is commonly used in an {@code @Bean} method and often combined with
* {@code @ConfigurationProperties}.
*
* @param <T> the {@link DataSource} type being built
* @author Dave Syer
* @author Madhura Bhave
* @author Fabio Grassi
* @author Phillip Webb
* @since 2.0.0
* @see #create()
* @see #create(ClassLoader)
* @see #derivedFrom(DataSource)
*/
public final class DataSourceBuilder<T extends DataSource> {
private final ClassLoader classLoader;
private final Map<DataSourceProperty, String> values = new HashMap<>();
private Class<T> type;
private final DataSource deriveFrom;
private DataSourceBuilder(ClassLoader classLoader) {
this.classLoader = classLoader;
this.deriveFrom = null;
}
@SuppressWarnings("unchecked")
private DataSourceBuilder(T deriveFrom) {
Assert.notNull(deriveFrom, "'deriveFrom' must not be null");
this.classLoader = deriveFrom.getClass().getClassLoader();
this.type = (Class<T>) deriveFrom.getClass();
this.deriveFrom = deriveFrom;
}
/**
* Set the {@link DataSource} type that should be built.
* @param <D> the datasource type
* @param type the datasource type
* @return this builder
*/
@SuppressWarnings("unchecked")
public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
this.type = (Class<T>) type;
return (DataSourceBuilder<D>) this;
}
/**
* Set the URL that should be used when building the datasource.
* @param url the JDBC url
* @return this builder
*/
public DataSourceBuilder<T> url(String url) {
set(DataSourceProperty.URL, url);
return this;
}
/**
* Set the driver class name that should be used when building the datasource.
* @param driverClassName the driver class name
* @return this builder
*/
public DataSourceBuilder<T> driverClassName(String driverClassName) {
set(DataSourceProperty.DRIVER_CLASS_NAME, driverClassName);
return this;
}
/**
* Set the username that should be used when building the datasource.
* @param username the user name
* @return this builder
*/
public DataSourceBuilder<T> username(String username) {
set(DataSourceProperty.USERNAME, username);
return this;
}
/**
* Set the password that should be used when building the datasource.
* @param password the password
* @return this builder
*/
public DataSourceBuilder<T> password(String password) {
set(DataSourceProperty.PASSWORD, password);
return this;
}
private void set(DataSourceProperty property, String value) {
this.values.put(property, value);
}
/**
* Return a newly built {@link DataSource} instance.
* @return the built datasource
*/
public T build() {
DataSourceProperties<T> properties = DataSourceProperties.forType(this.classLoader, this.type);
DataSourceProperties<DataSource> deriveFromProperties = getDeriveFromProperties();
Class<? extends T> instanceType = (this.type != null) ? this.type : properties.getDataSourceInstanceType();
T dataSource = BeanUtils.instantiateClass(instanceType);
Set<DataSourceProperty> applied = new HashSet<>();
for (DataSourceProperty property : DataSourceProperty.values()) {
String value = this.values.get(property);
if (value == null && deriveFromProperties != null && properties.canSet(property)) {
value = deriveFromProperties.get(this.deriveFrom, property);
}
if (value != null) {
properties.set(dataSource, property, value);
applied.add(property);
}
}
if (!applied.contains(DataSourceProperty.DRIVER_CLASS_NAME)
&& properties.canSet(DataSourceProperty.DRIVER_CLASS_NAME)
&& applied.contains(DataSourceProperty.URL)) {
String url = properties.get(dataSource, DataSourceProperty.URL);
DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url);
String driverClassName = driver.getDriverClassName();
if (driverClassName != null) {
properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName());
}
}
return dataSource;
}
@SuppressWarnings("unchecked")
private DataSourceProperties<DataSource> getDeriveFromProperties() {
if (this.deriveFrom == null) {
return null;
}
return DataSourceProperties.forType(this.classLoader, (Class<DataSource>) this.deriveFrom.getClass());
}
/**
* Create a new {@link DataSourceBuilder} instance.
* @return a new datasource builder instance
*/
public static DataSourceBuilder<?> create() {
return create(null);
}
/**
* Create a new {@link DataSourceBuilder} instance.
* @param classLoader the classloader used to discover preferred settings
* @return a new {@link DataSource} builder instance
*/
public static DataSourceBuilder<?> create(ClassLoader classLoader) {
return new DataSourceBuilder<>(classLoader);
}
/**
* Create a new {@link DataSourceBuilder} instance derived from the specified data
* source. The returned builder can be used to build the same type of
* {@link DataSource} with {@code username}, {@code password}, {@code url} and
* {@code driverClassName} properties copied from the original when not specifically
* set.
* @param dataSource the source {@link DataSource}
* @return a new {@link DataSource} builder
*/
public static DataSourceBuilder<?> derivedFrom(DataSource dataSource) {
return new DataSourceBuilder<>(unwrap(dataSource));
}
private static DataSource unwrap(DataSource dataSource) {
try {
while (dataSource.isWrapperFor(DataSource.class)) {
DataSource unwrapped = dataSource.unwrap(DataSource.class);
if (unwrapped == dataSource) {
return unwrapped;
}
dataSource = unwrapped;
}
}
catch (SQLException ex) {
// Try to continue with the existing, potentially still wrapped, DataSource
}
return dataSource;
}
/**
* Find the {@link DataSource} type preferred for the given classloader.
* @param classLoader the classloader used to discover preferred settings
* @return the preferred {@link DataSource} type
*/
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
MappedDataSourceProperties<?> mappings = MappedDataSourceProperties.forType(classLoader, null);
return (mappings != null) ? mappings.getDataSourceInstanceType() : null;
}
/**
* An individual DataSource property supported by the builder.
*/
private enum DataSourceProperty {
URL(false, "url", "URL"),
DRIVER_CLASS_NAME(true, "driverClassName"),
USERNAME(false, "username", "user"),
PASSWORD(false, "password");
private final boolean optional;
private final String[] names;
DataSourceProperty(boolean optional, String... names) {
this.optional = optional;
this.names = names;
}
boolean isOptional() {
return this.optional;
}
@Override
public String toString() {
return this.names[0];
}
Method findSetter(Class<?> type) {
return findMethod("set", type, String.class);
}
Method findGetter(Class<?> type) {
return findMethod("get", type);
}
private 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);
if (method != null) {
return method;
}
}
return null;
}
}
private interface DataSourceProperties<T extends DataSource> {
Class<? extends T> getDataSourceInstanceType();
boolean canSet(DataSourceProperty property);
void set(T dataSource, DataSourceProperty property, String value);
String get(T dataSource, DataSourceProperty property);
static <T extends DataSource> DataSourceProperties<T> forType(ClassLoader classLoader, Class<T> type) {
MappedDataSourceProperties<T> mapped = MappedDataSourceProperties.forType(classLoader, type);
return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type);
}
}
private static class MappedDataSourceProperties<T extends DataSource> implements DataSourceProperties<T> {
private final Map<DataSourceProperty, MappedDataSourceProperty<T, ?>> mappedProperties = new HashMap<>();
private final Class<T> dataSourceType;
@SuppressWarnings("unchecked")
MappedDataSourceProperties() {
this.dataSourceType = (Class<T>) ResolvableType.forClass(MappedDataSourceProperties.class, getClass())
.resolveGeneric();
}
@Override
public Class<? extends T> getDataSourceInstanceType() {
return this.dataSourceType;
}
protected void add(DataSourceProperty property, 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) {
this.mappedProperties.put(property, new MappedDataSourceProperty<>(property, type, getter, setter));
}
@Override
public boolean canSet(DataSourceProperty property) {
return this.mappedProperties.containsKey(property);
}
@Override
public void set(T dataSource, DataSourceProperty property, String value) {
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
if (mappedProperty != null) {
mappedProperty.set(dataSource, value);
}
}
@Override
public String get(T dataSource, DataSourceProperty property) {
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
if (mappedProperty != null) {
return mappedProperty.get(dataSource);
}
return null;
}
private 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) {
MappedDataSourceProperties<T> pooled = lookupPooled(classLoader, type);
if (type == null || pooled != null) {
return pooled;
}
return lookupBasic(classLoader, type);
}
private static <T extends DataSource> MappedDataSourceProperties<T> lookupPooled(ClassLoader classLoader,
Class<T> type) {
MappedDataSourceProperties<T> result = null;
result = lookup(classLoader, type, result, "com.zaxxer.hikari.HikariDataSource",
HikariDataSourceProperties::new);
result = lookup(classLoader, type, result, "org.apache.tomcat.jdbc.pool.DataSource",
TomcatPoolDataSourceProperties::new);
result = lookup(classLoader, type, result, "org.apache.commons.dbcp2.BasicDataSource",
MappedDbcp2DataSource::new);
result = lookup(classLoader, type, result, "oracle.ucp.jdbc.PoolDataSourceImpl",
OraclePoolDataSourceProperties::new, "oracle.jdbc.OracleConnection");
result = lookup(classLoader, type, result, "com.mchange.v2.c3p0.ComboPooledDataSource",
ComboPooledDataSourceProperties::new);
result = lookup(classLoader, type, result, "org.vibur.dbcp.ViburDBCPDataSource",
ViburDataSourceProperties::new);
return result;
}
private static <T extends DataSource> MappedDataSourceProperties<T> lookupBasic(ClassLoader classLoader,
Class<T> dataSourceType) {
MappedDataSourceProperties<T> result = null;
result = lookup(classLoader, dataSourceType, result,
"org.springframework.jdbc.datasource.SimpleDriverDataSource", SimpleDataSourceProperties::new);
result = lookup(classLoader, dataSourceType, result, "oracle.jdbc.datasource.OracleDataSource",
OracleDataSourceProperties::new);
result = lookup(classLoader, dataSourceType, result, "org.h2.jdbcx.JdbcDataSource",
H2DataSourceProperties::new);
result = lookup(classLoader, dataSourceType, result, "org.postgresql.ds.PGSimpleDataSource",
PostgresDataSourceProperties::new);
return result;
}
@SuppressWarnings("unchecked")
private static <T extends DataSource> MappedDataSourceProperties<T> lookup(ClassLoader classLoader,
Class<T> dataSourceType, MappedDataSourceProperties<T> existing, String dataSourceClassName,
Supplier<MappedDataSourceProperties<?>> propertyMappingsSupplier, String... requiredClassNames) {
if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) {
return existing;
}
MappedDataSourceProperties<?> propertyMappings = propertyMappingsSupplier.get();
return (dataSourceType == null
|| propertyMappings.getDataSourceInstanceType().isAssignableFrom(dataSourceType))
? (MappedDataSourceProperties<T>) propertyMappings : null;
}
private static boolean allPresent(ClassLoader classLoader, String dataSourceClassName,
String[] requiredClassNames) {
boolean result = ClassUtils.isPresent(dataSourceClassName, classLoader);
for (String requiredClassName : requiredClassNames) {
result = result && ClassUtils.isPresent(requiredClassName, classLoader);
}
return result;
}
}
private static class MappedDataSourceProperty<T extends DataSource, V> {
private final DataSourceProperty property;
private final Class<V> type;
private final Getter<T, V> getter;
private final Setter<T, V> setter;
MappedDataSourceProperty(DataSourceProperty property, Class<V> type, Getter<T, V> getter, Setter<T, V> setter) {
this.property = property;
this.type = type;
this.getter = getter;
this.setter = setter;
}
void set(T dataSource, String value) {
try {
if (this.setter == null) {
UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(),
() -> "No setter mapped for '" + this.property + "' property");
return;
}
this.setter.set(dataSource, convertFromString(value));
}
catch (SQLException ex) {
throw new IllegalStateException(ex);
}
}
String get(T dataSource) {
try {
if (this.getter == null) {
UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(),
() -> "No getter mapped for '" + this.property + "' property");
return null;
}
return convertToString(this.getter.get(dataSource));
}
catch (SQLException ex) {
throw new IllegalStateException(ex);
}
}
@SuppressWarnings("unchecked")
private V convertFromString(String value) {
if (String.class.equals(this.type)) {
return (V) value;
}
if (Class.class.equals(this.type)) {
return (V) ClassUtils.resolveClassName(value, null);
}
throw new IllegalStateException("Unsupported value type " + this.type);
}
private String convertToString(V value) {
if (String.class.equals(this.type)) {
return (String) value;
}
if (Class.class.equals(this.type)) {
return ((Class<?>) value).getName();
}
throw new IllegalStateException("Unsupported value type " + this.type);
}
}
private static class ReflectionDataSourceProperties<T extends DataSource> implements DataSourceProperties<T> {
private final Map<DataSourceProperty, Method> getters;
private final Map<DataSourceProperty, Method> setters;
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()) {
putIfNotNull(getters, property, property.findGetter(dataSourceType));
putIfNotNull(setters, property, property.findSetter(dataSourceType));
}
this.dataSourceType = dataSourceType;
this.getters = Collections.unmodifiableMap(getters);
this.setters = Collections.unmodifiableMap(setters);
}
private void putIfNotNull(Map<DataSourceProperty, Method> map, DataSourceProperty property, Method method) {
if (method != null) {
map.put(property, method);
}
}
@Override
public Class<T> getDataSourceInstanceType() {
return this.dataSourceType;
}
@Override
public boolean canSet(DataSourceProperty property) {
return this.setters.containsKey(property);
}
@Override
public void set(T dataSource, DataSourceProperty property, String value) {
Method method = getMethod(property, this.setters);
if (method != null) {
ReflectionUtils.invokeMethod(method, dataSource, value);
}
}
@Override
public String get(T dataSource, DataSourceProperty property) {
Method method = getMethod(property, this.getters);
if (method != null) {
return (String) ReflectionUtils.invokeMethod(method, dataSource);
}
return null;
}
private Method getMethod(DataSourceProperty property, Map<DataSourceProperty, Method> methods) {
Method method = methods.get(property);
if (method == null) {
UnsupportedDataSourcePropertyException.throwIf(!property.isOptional(),
() -> "Unable to find suitable method for " + property);
return null;
}
ReflectionUtils.makeAccessible(method);
return method;
}
}
@FunctionalInterface
private interface Getter<T, V> {
V get(T instance) throws SQLException;
}
@FunctionalInterface
private interface Setter<T, V> {
void set(T instance, V value) throws SQLException;
}
/**
* {@link DataSourceProperties} for Hikari.
*/
private static class HikariDataSourceProperties extends MappedDataSourceProperties<HikariDataSource> {
HikariDataSourceProperties() {
add(DataSourceProperty.URL, HikariDataSource::getJdbcUrl, HikariDataSource::setJdbcUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, HikariDataSource::getDriverClassName,
HikariDataSource::setDriverClassName);
add(DataSourceProperty.USERNAME, HikariDataSource::getUsername, HikariDataSource::setUsername);
add(DataSourceProperty.PASSWORD, HikariDataSource::getPassword, HikariDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for Tomcat Pool.
*/
private static class TomcatPoolDataSourceProperties
extends MappedDataSourceProperties<org.apache.tomcat.jdbc.pool.DataSource> {
TomcatPoolDataSourceProperties() {
add(DataSourceProperty.URL, org.apache.tomcat.jdbc.pool.DataSource::getUrl,
org.apache.tomcat.jdbc.pool.DataSource::setUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, org.apache.tomcat.jdbc.pool.DataSource::getDriverClassName,
org.apache.tomcat.jdbc.pool.DataSource::setDriverClassName);
add(DataSourceProperty.USERNAME, org.apache.tomcat.jdbc.pool.DataSource::getUsername,
org.apache.tomcat.jdbc.pool.DataSource::setUsername);
add(DataSourceProperty.PASSWORD, org.apache.tomcat.jdbc.pool.DataSource::getPassword,
org.apache.tomcat.jdbc.pool.DataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for DBCP2.
*/
private static class MappedDbcp2DataSource extends MappedDataSourceProperties<BasicDataSource> {
MappedDbcp2DataSource() {
add(DataSourceProperty.URL, BasicDataSource::getUrl, BasicDataSource::setUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, BasicDataSource::getDriverClassName,
BasicDataSource::setDriverClassName);
add(DataSourceProperty.USERNAME, BasicDataSource::getUserName, BasicDataSource::setUsername);
add(DataSourceProperty.PASSWORD, null, BasicDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for Oracle Pool.
*/
private static class OraclePoolDataSourceProperties extends MappedDataSourceProperties<PoolDataSource> {
@Override
public Class<? extends PoolDataSource> getDataSourceInstanceType() {
return PoolDataSourceImpl.class;
}
OraclePoolDataSourceProperties() {
add(DataSourceProperty.URL, PoolDataSource::getURL, PoolDataSource::setURL);
add(DataSourceProperty.DRIVER_CLASS_NAME, PoolDataSource::getConnectionFactoryClassName,
PoolDataSource::setConnectionFactoryClassName);
add(DataSourceProperty.USERNAME, PoolDataSource::getUser, PoolDataSource::setUser);
add(DataSourceProperty.PASSWORD, null, PoolDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for C3P0.
*/
private static class ComboPooledDataSourceProperties extends MappedDataSourceProperties<ComboPooledDataSource> {
ComboPooledDataSourceProperties() {
add(DataSourceProperty.URL, ComboPooledDataSource::getJdbcUrl, ComboPooledDataSource::setJdbcUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, ComboPooledDataSource::getDriverClass, this::setDriverClass);
add(DataSourceProperty.USERNAME, ComboPooledDataSource::getUser, ComboPooledDataSource::setUser);
add(DataSourceProperty.PASSWORD, ComboPooledDataSource::getPassword, ComboPooledDataSource::setPassword);
}
private void setDriverClass(ComboPooledDataSource dataSource, String driverClass) {
try {
dataSource.setDriverClass(driverClass);
}
catch (PropertyVetoException ex) {
throw new IllegalArgumentException(ex);
}
}
}
private static class ViburDataSourceProperties extends MappedDataSourceProperties<ViburDBCPDataSource> {
ViburDataSourceProperties() {
add(DataSourceProperty.URL, ViburDBCPDataSource::getJdbcUrl, ViburDBCPDataSource::setJdbcUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, ViburDBCPDataSource::getDriverClassName,
ViburDBCPDataSource::setDriverClassName);
add(DataSourceProperty.USERNAME, ViburDBCPDataSource::getUsername, ViburDBCPDataSource::setUsername);
add(DataSourceProperty.PASSWORD, ViburDBCPDataSource::getPassword, ViburDBCPDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for Spring's {@link SimpleDriverDataSource}.
*/
private static class SimpleDataSourceProperties extends MappedDataSourceProperties<SimpleDriverDataSource> {
@SuppressWarnings("unchecked")
SimpleDataSourceProperties() {
add(DataSourceProperty.URL, SimpleDriverDataSource::getUrl, SimpleDriverDataSource::setUrl);
add(DataSourceProperty.DRIVER_CLASS_NAME, Class.class, (dataSource) -> dataSource.getDriver().getClass(),
SimpleDriverDataSource::setDriverClass);
add(DataSourceProperty.USERNAME, SimpleDriverDataSource::getUsername, SimpleDriverDataSource::setUsername);
add(DataSourceProperty.PASSWORD, SimpleDriverDataSource::getPassword, SimpleDriverDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for Oracle.
*/
private static class OracleDataSourceProperties extends MappedDataSourceProperties<OracleDataSource> {
OracleDataSourceProperties() {
add(DataSourceProperty.URL, OracleDataSource::getURL, OracleDataSource::setURL);
add(DataSourceProperty.USERNAME, OracleDataSource::getUser, OracleDataSource::setUser);
add(DataSourceProperty.PASSWORD, null, OracleDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for H2.
*/
private static class H2DataSourceProperties extends MappedDataSourceProperties<JdbcDataSource> {
H2DataSourceProperties() {
add(DataSourceProperty.URL, JdbcDataSource::getUrl, JdbcDataSource::setUrl);
add(DataSourceProperty.USERNAME, JdbcDataSource::getUser, JdbcDataSource::setUser);
add(DataSourceProperty.PASSWORD, JdbcDataSource::getPassword, JdbcDataSource::setPassword);
}
}
/**
* {@link DataSourceProperties} for Postgres.
*/
private static class PostgresDataSourceProperties extends MappedDataSourceProperties<PGSimpleDataSource> {
PostgresDataSourceProperties() {
add(DataSourceProperty.URL, PGSimpleDataSource::getUrl, PGSimpleDataSource::setUrl);
add(DataSourceProperty.USERNAME, PGSimpleDataSource::getUser, PGSimpleDataSource::setUser);
add(DataSourceProperty.PASSWORD, PGSimpleDataSource::getPassword, PGSimpleDataSource::setPassword);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
/**
* {@link RuntimeHintsRegistrar} implementation for {@link DataSource} types supported by
* the {@link DataSourceBuilder}.
*
* @author Phillip Webb
*/
class DataSourceBuilderRuntimeHints implements RuntimeHintsRegistrar {
private static final List<String> TYPE_NAMES;
static {
List<String> typeNames = new ArrayList<>();
typeNames.add("com.mchange.v2.c3p0.ComboPooledDataSource");
typeNames.add("com.zaxxer.hikari.HikariDataSource");
typeNames.add("oracle.jdbc.datasource.OracleDataSource");
typeNames.add("oracle.ucp.jdbc.PoolDataSource");
typeNames.add("org.apache.commons.dbcp2.BasicDataSource");
typeNames.add("org.apache.tomcat.jdbc.pool.DataSource");
typeNames.add("org.h2.jdbcx.JdbcDataSource");
typeNames.add("org.postgresql.ds.PGSimpleDataSource");
typeNames.add("org.springframework.jdbc.datasource.SimpleDriverDataSource");
typeNames.add("org.vibur.dbcp.ViburDBCPDataSource");
TYPE_NAMES = Collections.unmodifiableList(typeNames);
}
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
for (String typeName : TYPE_NAMES) {
hints.reflection()
.registerTypeIfPresent(classLoader, typeName,
(hint) -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.sql.Wrapper;
import javax.sql.DataSource;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import org.springframework.util.ClassUtils;
/**
* Unwraps a {@link DataSource} that may have been proxied or wrapped in a custom
* {@link Wrapper} such as {@link DelegatingDataSource}.
*
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
* @since 2.0.7
*/
public final class DataSourceUnwrapper {
private static final boolean DELEGATING_DATA_SOURCE_PRESENT = ClassUtils.isPresent(
"org.springframework.jdbc.datasource.DelegatingDataSource", DataSourceUnwrapper.class.getClassLoader());
private DataSourceUnwrapper() {
}
/**
* Return an object that implements the given {@code target} type, unwrapping delegate
* or proxy if necessary using the specified {@code unwrapInterface}.
* @param dataSource the datasource to handle
* @param unwrapInterface the interface that the target type must implement
* @param target the type that the result must implement
* @param <I> the interface that the target type must implement
* @param <T> the target type
* @return an object that implements the target type or {@code null}
* @since 2.3.8
* @see Wrapper#unwrap(Class)
*/
public static <I, T extends I> T unwrap(DataSource dataSource, Class<I> unwrapInterface, Class<T> target) {
if (target.isInstance(dataSource)) {
return target.cast(dataSource);
}
I unwrapped = safeUnwrap(dataSource, unwrapInterface);
if (unwrapped != null && unwrapInterface.isAssignableFrom(target)) {
return target.cast(unwrapped);
}
if (DELEGATING_DATA_SOURCE_PRESENT) {
DataSource targetDataSource = DelegatingDataSourceUnwrapper.getTargetDataSource(dataSource);
if (targetDataSource != null) {
return unwrap(targetDataSource, unwrapInterface, target);
}
}
if (AopUtils.isAopProxy(dataSource)) {
Object proxyTarget = AopProxyUtils.getSingletonTarget(dataSource);
if (proxyTarget instanceof DataSource proxyDataSource) {
return unwrap(proxyDataSource, unwrapInterface, target);
}
}
return null;
}
/**
* Return an object that implements the given {@code target} type, unwrapping delegate
* or proxy if necessary. Consider using {@link #unwrap(DataSource, Class, Class)} as
* {@link Wrapper#unwrap(Class) unwrapping} won't be considered if {@code target} is
* not an interface.
* @param dataSource the datasource to handle
* @param target the type that the result must implement
* @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) {
return unwrap(dataSource, target, target);
}
private static <S> S safeUnwrap(Wrapper wrapper, Class<S> target) {
try {
if (target.isInterface() && wrapper.isWrapperFor(target)) {
return wrapper.unwrap(target);
}
}
catch (Exception ex) {
// Continue
}
return null;
}
private static final class DelegatingDataSourceUnwrapper {
private static DataSource getTargetDataSource(DataSource dataSource) {
if (dataSource instanceof DelegatingDataSource delegatingDataSource) {
return delegatingDataSource.getTargetDataSource();
}
return null;
}
}
}

View File

@@ -0,0 +1,340 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Enumeration of common database drivers.
*
* @author Phillip Webb
* @author Maciej Walkowiak
* @author Marten Deinum
* @author Stephane Nicoll
* @since 1.4.0
*/
public enum DatabaseDriver {
/**
* Unknown type.
*/
UNKNOWN(null, null),
/**
* Apache Derby.
*/
DERBY("Apache Derby", "org.apache.derby.jdbc.EmbeddedDriver", "org.apache.derby.jdbc.EmbeddedXADataSource",
"SELECT 1 FROM SYSIBM.SYSDUMMY1"),
/**
* H2.
*/
H2("H2", "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource", "SELECT 1"),
/**
* HyperSQL DataBase.
*/
HSQLDB("HSQL Database Engine", "org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource",
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS"),
/**
* SQLite.
*/
SQLITE("SQLite", "org.sqlite.JDBC"),
/**
* MySQL.
*/
MYSQL("MySQL", "com.mysql.cj.jdbc.Driver", "com.mysql.cj.jdbc.MysqlXADataSource", "/* ping */ SELECT 1"),
/**
* Maria DB.
*/
MARIADB("MariaDB", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource", "SELECT 1"),
/**
* Oracle.
*/
ORACLE("Oracle", "oracle.jdbc.OracleDriver", "oracle.jdbc.xa.client.OracleXADataSource",
"SELECT 'Hello' from DUAL"),
/**
* Postgres.
*/
POSTGRESQL("PostgreSQL", "org.postgresql.Driver", "org.postgresql.xa.PGXADataSource", "SELECT 1"),
/**
* Amazon Redshift.
* @since 2.2.0
*/
REDSHIFT("Redshift", "com.amazon.redshift.jdbc.Driver", null, "SELECT 1"),
/**
* HANA - SAP HANA Database - HDB.
* @since 2.1.0
*/
HANA("HDB", "com.sap.db.jdbc.Driver", "com.sap.db.jdbcext.XADataSourceSAP", "SELECT 1 FROM SYS.DUMMY") {
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("sap");
}
},
/**
* jTDS. As it can be used for several databases, there isn't a single product name we
* could rely on.
*/
JTDS(null, "net.sourceforge.jtds.jdbc.Driver"),
/**
* SQL Server.
*/
SQLSERVER("Microsoft SQL Server", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource", "SELECT 1") {
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || "SQL SERVER".equalsIgnoreCase(productName);
}
},
/**
* Firebird.
*/
FIREBIRD("Firebird", "org.firebirdsql.jdbc.FBDriver", "org.firebirdsql.ds.FBXADataSource",
"SELECT 1 FROM RDB$DATABASE") {
@Override
protected Collection<String> getUrlPrefixes() {
return Arrays.asList("firebirdsql", "firebird");
}
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName)
|| productName.toLowerCase(Locale.ENGLISH).startsWith("firebird");
}
},
/**
* DB2 Server.
*/
DB2("DB2", "com.ibm.db2.jcc.DB2Driver", "com.ibm.db2.jcc.DB2XADataSource", "SELECT 1 FROM SYSIBM.SYSDUMMY1") {
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || productName.toLowerCase(Locale.ENGLISH).startsWith("db2/");
}
},
/**
* DB2 AS400 Server.
*/
DB2_AS400("DB2 UDB for AS/400", "com.ibm.as400.access.AS400JDBCDriver",
"com.ibm.as400.access.AS400JDBCXADataSource", "SELECT 1 FROM SYSIBM.SYSDUMMY1") {
@Override
public String getId() {
return "db2";
}
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("as400");
}
@Override
protected boolean matchProductName(String productName) {
return super.matchProductName(productName) || productName.toLowerCase(Locale.ENGLISH).contains("as/400");
}
},
/**
* Teradata.
*/
TERADATA("Teradata", "com.teradata.jdbc.TeraDriver"),
/**
* Informix.
*/
INFORMIX("Informix Dynamic Server", "com.informix.jdbc.IfxDriver", null, "select count(*) from systables") {
@Override
protected Collection<String> getUrlPrefixes() {
return Arrays.asList("informix-sqli", "informix-direct");
}
},
/**
* Apache Phoenix.
* @since 2.5.0
*/
PHOENIX("Apache Phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", null, "SELECT 1 FROM SYSTEM.CATALOG LIMIT 1"),
/**
* Testcontainers.
*/
TESTCONTAINERS(null, "org.testcontainers.jdbc.ContainerDatabaseDriver") {
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("tc");
}
},
/**
* ClickHouse.
* @since 3.4.0
*/
CLICKHOUSE("ClickHouse", "com.clickhouse.jdbc.ClickHouseDriver", null, "SELECT 1") {
@Override
protected Collection<String> getUrlPrefixes() {
return Arrays.asList("ch", "clickhouse");
}
},
/**
* AWS Advanced JDBC Wrapper.
* @since 3.5.0
*/
AWS_WRAPPER(null, "software.amazon.jdbc.Driver") {
@Override
protected Collection<String> getUrlPrefixes() {
return Collections.singleton("aws-wrapper");
}
};
private final String productName;
private final String driverClassName;
private final String xaDataSourceClassName;
private final String validationQuery;
DatabaseDriver(String productName, String driverClassName) {
this(productName, driverClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName) {
this(productName, driverClassName, xaDataSourceClassName, null);
}
DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName, String validationQuery) {
this.productName = productName;
this.driverClassName = driverClassName;
this.xaDataSourceClassName = xaDataSourceClassName;
this.validationQuery = validationQuery;
}
/**
* Return the identifier of this driver.
* @return the identifier
*/
public String getId() {
return name().toLowerCase(Locale.ENGLISH);
}
/**
* Return the url prefixes of this driver.
* @return the url prefixes
*/
protected Collection<String> getUrlPrefixes() {
return Collections.singleton(name().toLowerCase(Locale.ENGLISH));
}
protected boolean matchProductName(String productName) {
return this.productName != null && this.productName.equalsIgnoreCase(productName);
}
/**
* Return the driver class name.
* @return the class name or {@code null}
*/
public String getDriverClassName() {
return this.driverClassName;
}
/**
* Return the XA driver source class name.
* @return the class name or {@code null}
*/
public String getXaDataSourceClassName() {
return this.xaDataSourceClassName;
}
/**
* Return the validation query.
* @return the validation query or {@code null}
*/
public String getValidationQuery() {
return this.validationQuery;
}
/**
* Find a {@link DatabaseDriver} for the given URL.
* @param url the JDBC URL
* @return the database driver or {@link #UNKNOWN} if not found
*/
public static DatabaseDriver fromJdbcUrl(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);
for (DatabaseDriver driver : values()) {
for (String urlPrefix : driver.getUrlPrefixes()) {
String prefix = ":" + urlPrefix + ":";
if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
return driver;
}
}
}
}
return UNKNOWN;
}
/**
* Find a {@link DatabaseDriver} for the given product name.
* @param productName product name
* @return the database driver or {@link #UNKNOWN} if not found
*/
public static DatabaseDriver fromProductName(String productName) {
if (StringUtils.hasLength(productName)) {
for (DatabaseDriver candidate : values()) {
if (candidate.matchProductName(productName)) {
return candidate;
}
}
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Locale;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.ThrowingFunction;
/**
* Connection details for {@link EmbeddedDatabaseType embedded databases}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Stephane Nicoll
* @author Nidhi Desai
* @author Moritz Halbritter
* @since 1.0.0
* @see #get(ClassLoader)
*/
public enum EmbeddedDatabaseConnection {
/**
* No Connection.
*/
NONE(null),
/**
* H2 Database Connection.
*/
H2("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"),
/**
* Derby Database Connection.
*/
DERBY("jdbc:derby:memory:%s;create=true"),
/**
* HSQL Database Connection.
* @since 2.4.0
*/
HSQLDB("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s");
private final String alternativeDriverClass;
private final String url;
EmbeddedDatabaseConnection(String url) {
this(null, url);
}
EmbeddedDatabaseConnection(String fallbackDriverClass, String url) {
this.alternativeDriverClass = fallbackDriverClass;
this.url = url;
}
/**
* Returns the driver class name.
* @return the driver class name
*/
public String getDriverClassName() {
// See https://github.com/spring-projects/spring-boot/issues/32865
return switch (this) {
case NONE -> null;
case H2 -> DatabaseDriver.H2.getDriverClassName();
case DERBY -> DatabaseDriver.DERBY.getDriverClassName();
case HSQLDB -> DatabaseDriver.HSQLDB.getDriverClassName();
};
}
/**
* Returns the {@link EmbeddedDatabaseType} for the connection.
* @return the database type
*/
public EmbeddedDatabaseType getType() {
// See https://github.com/spring-projects/spring-boot/issues/32865
return switch (this) {
case NONE -> null;
case H2 -> EmbeddedDatabaseType.H2;
case DERBY -> EmbeddedDatabaseType.DERBY;
case HSQLDB -> EmbeddedDatabaseType.HSQL;
};
}
/**
* Returns the URL for the connection using the specified {@code databaseName}.
* @param databaseName the name of the database
* @return the connection URL
*/
public String getUrl(String databaseName) {
Assert.hasText(databaseName, "'databaseName' must not be empty");
return (this.url != null) ? String.format(this.url, databaseName) : null;
}
boolean isEmbeddedUrl(String url) {
// See https://github.com/spring-projects/spring-boot/issues/32865
return switch (this) {
case NONE -> false;
case H2 -> url.contains(":h2:mem");
case DERBY -> true;
case HSQLDB -> url.contains(":hsqldb:mem:");
};
}
boolean isDriverCompatible(String driverClass) {
return (driverClass != null
&& (driverClass.equals(getDriverClassName()) || driverClass.equals(this.alternativeDriverClass)));
}
/**
* Convenience method to determine if a given driver class name and url represent an
* embedded database type.
* @param driverClass the driver class
* @param url the jdbc url (can be {@code null})
* @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) {
if (driverClass == null) {
return false;
}
EmbeddedDatabaseConnection connection = getEmbeddedDatabaseConnection(driverClass);
if (connection == NONE) {
return false;
}
return (url == null || connection.isEmbeddedUrl(url));
}
private static EmbeddedDatabaseConnection getEmbeddedDatabaseConnection(String driverClass) {
return Stream.of(H2, HSQLDB, DERBY)
.filter((connection) -> connection.isDriverCompatible(driverClass))
.findFirst()
.orElse(NONE);
}
/**
* Convenience method to determine if a given data source represents an embedded
* database type.
* @param dataSource the data source to interrogate
* @return true if the data source is one of the embedded types
*/
public static boolean isEmbedded(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
return new IsEmbedded().apply(connection);
}
catch (SQLException ex) {
// Could not connect, which means it's not embedded
return false;
}
}
/**
* Returns the most suitable {@link EmbeddedDatabaseConnection} for the given class
* loader.
* @param classLoader the class loader used to check for classes
* @return an {@link EmbeddedDatabaseConnection} or {@link #NONE}.
*/
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) {
return candidate;
}
}
return NONE;
}
/**
* Determine if a {@link Connection} is embedded.
*/
private static final class IsEmbedded implements ThrowingFunction<Connection, Boolean> {
@Override
public Boolean applyWithException(Connection connection) throws SQLException, DataAccessException {
DatabaseMetaData metaData = connection.getMetaData();
String productName = metaData.getDatabaseProductName();
if (productName == null) {
return false;
}
productName = productName.toUpperCase(Locale.ENGLISH);
EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values();
for (EmbeddedDatabaseConnection candidate : candidates) {
if (candidate != NONE && productName.contains(candidate.getType().name())) {
String url = metaData.getURL();
return (url == null || candidate.isEmbeddedUrl(url));
}
}
return false;
}
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
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.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link Lifecycle} for a {@link HikariDataSource} allowing it to participate in
* checkpoint-restore. When {@link #stop() stopped}, and the data source
* {@link HikariDataSource#isAllowPoolSuspension() allows it}, its pool is suspended,
* blocking any attempts to borrow connections. Open and idle connections are then
* evicted. When subsequently {@link #start() started}, the pool is
* {@link HikariPoolMXBean#resumePool() resumed} if necessary.
*
* @author Christoph Strobl
* @author Andy Wilkinson
* @author Moritz Halbritter
* @since 3.2.0
*/
public class HikariCheckpointRestoreLifecycle implements Lifecycle {
private static final Log logger = LogFactory.getLog(HikariCheckpointRestoreLifecycle.class);
private static final Field CLOSE_CONNECTION_EXECUTOR;
static {
Field closeConnectionExecutor = ReflectionUtils.findField(HikariPool.class, "closeConnectionExecutor");
Assert.state(closeConnectionExecutor != null, "Unable to locate closeConnectionExecutor for HikariPool");
Assert.state(ThreadPoolExecutor.class.isAssignableFrom(closeConnectionExecutor.getType()),
() -> "Expected ThreadPoolExecutor for closeConnectionExecutor but found %s"
.formatted(closeConnectionExecutor.getType()));
ReflectionUtils.makeAccessible(closeConnectionExecutor);
CLOSE_CONNECTION_EXECUTOR = closeConnectionExecutor;
}
private final Function<HikariPool, Boolean> hasOpenConnections;
private final HikariDataSource dataSource;
private final ConfigurableApplicationContext applicationContext;
/**
* Creates a new {@code HikariCheckpointRestoreLifecycle} that will allow the given
* {@code dataSource} to participate in checkpoint-restore. The {@code dataSource} is
* {@link DataSourceUnwrapper#unwrap unwrapped} to a {@link HikariDataSource}. If such
* unwrapping is not possible, the lifecycle will have no effect.
* @param dataSource the checkpoint-restore participant
* @deprecated since 3.4.0 for removal in 4.0.0 in favor of
* {@link #HikariCheckpointRestoreLifecycle(DataSource, ConfigurableApplicationContext)}
*/
@Deprecated(since = "3.4.0", forRemoval = true)
public HikariCheckpointRestoreLifecycle(DataSource dataSource) {
this(dataSource, null);
}
/**
* Creates a new {@code HikariCheckpointRestoreLifecycle} that will allow the given
* {@code dataSource} to participate in checkpoint-restore. The {@code dataSource} is
* {@link DataSourceUnwrapper#unwrap unwrapped} to a {@link HikariDataSource}. If such
* unwrapping is not possible, the lifecycle will have no effect.
* @param dataSource the checkpoint-restore participant
* @param applicationContext the application context
* @since 3.4.0
*/
public HikariCheckpointRestoreLifecycle(DataSource dataSource, ConfigurableApplicationContext applicationContext) {
this.dataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class);
this.applicationContext = applicationContext;
this.hasOpenConnections = (pool) -> {
ThreadPoolExecutor closeConnectionExecutor = (ThreadPoolExecutor) ReflectionUtils
.getField(CLOSE_CONNECTION_EXECUTOR, pool);
Assert.state(closeConnectionExecutor != null, "'closeConnectionExecutor' was null");
return closeConnectionExecutor.getActiveCount() > 0;
};
}
@Override
public void start() {
if (this.dataSource == null || this.dataSource.isRunning()) {
return;
}
Assert.state(!this.dataSource.isClosed(), "DataSource has been closed and cannot be restarted");
if (this.dataSource.isAllowPoolSuspension()) {
logger.info("Resuming Hikari pool");
this.dataSource.getHikariPoolMXBean().resumePool();
}
}
@Override
public void stop() {
if (this.dataSource == null || !this.dataSource.isRunning()) {
return;
}
if (this.dataSource.isAllowPoolSuspension()) {
logger.info("Suspending Hikari pool");
this.dataSource.getHikariPoolMXBean().suspendPool();
}
else {
if (this.applicationContext != null && !this.applicationContext.isClosed()) {
logger.warn(this.dataSource + " is not configured to allow pool suspension. "
+ "This will cause problems when the application is checkpointed. "
+ "Please configure allow-pool-suspension to fix this!");
}
}
closeConnections(Duration.ofMillis(this.dataSource.getConnectionTimeout() + 250));
}
private void closeConnections(Duration shutdownTimeout) {
logger.info("Evicting Hikari connections");
this.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);
try {
allConnectionsClosed.get(shutdownTimeout.toMillis(), TimeUnit.MILLISECONDS);
logger.debug("Hikari connections closed");
}
catch (InterruptedException ex) {
logger.warn("Interrupted while waiting for connections to be closed", ex);
Thread.currentThread().interrupt();
}
catch (TimeoutException ex) {
logger.warn(LogMessage.format("Hikari connections could not be closed within %s", shutdownTimeout), ex);
}
catch (ExecutionException ex) {
throw new RuntimeException("Failed to close Hikari connections", ex);
}
}
private void waitForConnectionsToClose() {
while (this.hasOpenConnections.apply((HikariPool) this.dataSource.getHikariPoolMXBean())) {
try {
TimeUnit.MILLISECONDS.sleep(50);
}
catch (InterruptedException ex) {
logger.error("Interrupted while waiting for datasource connections to be closed", ex);
Thread.currentThread().interrupt();
}
}
}
@Override
public boolean isRunning() {
return this.dataSource != null && this.dataSource.isRunning();
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
/**
* An enumeration of the available schema management options.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public enum SchemaManagement {
/**
* The schema is managed and will be created at the appropriate time.
*/
MANAGED,
/**
* The schema is not managed.
*/
UNMANAGED
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import javax.sql.DataSource;
/**
* Strategy interface to determine the {@link SchemaManagement} of a {@link DataSource}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@FunctionalInterface
public interface SchemaManagementProvider {
/**
* Return the {@link SchemaManagement} for the specified {@link DataSource}.
* @param dataSource the dataSource to handle
* @return the {@link SchemaManagement} for the {@link DataSource}.
*/
SchemaManagement getSchemaManagement(DataSource dataSource);
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.Set;
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.simple.JdbcClient;
/**
* {@link DependsOnDatabaseInitializationDetector} for Spring Framework's JDBC support.
*
* @author Andy Wilkinson
*/
class SpringJdbcDependsOnDatabaseInitializationDetector
extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes() {
return Set.of(JdbcClient.class, JdbcOperations.class, NamedParameterJdbcOperations.class);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.function.Supplier;
/**
* {@link RuntimeException} thrown from {@link DataSourceBuilder} when an unsupported
* property is used.
*
* @author Phillip Webb
* @since 2.5.0
*/
public class UnsupportedDataSourcePropertyException extends RuntimeException {
UnsupportedDataSourcePropertyException(String message) {
super(message);
}
static void throwIf(boolean test, Supplier<String> message) {
if (test) {
throw new UnsupportedDataSourcePropertyException(message.get());
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import jakarta.transaction.TransactionManager;
/**
* Strategy interface used to wrap an {@link XADataSource} enrolling it with a JTA
* {@link TransactionManager}.
*
* @author Phillip Webb
* @since 2.0.0
*/
@FunctionalInterface
public interface XADataSourceWrapper {
/**
* Wrap the specific {@link XADataSource} and enroll it with a JTA
* {@link TransactionManager}.
* @param dataSource the data source to wrap
* @return the wrapped data source
* @throws Exception if the data source cannot be wrapped
*/
DataSource wrapDataSource(XADataSource dataSource) throws Exception;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.sql.init.ApplicationScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
/**
* {@link DataSourceScriptDatabaseInitializer} for the primary SQL database. May be
* registered as a bean to override auto-configuration.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 4.0.0
*/
public class ApplicationDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer
implements ApplicationScriptDatabaseInitializer {
/**
* Create a new {@link ApplicationDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the primary SQL data source
* @param properties the SQL initialization properties
*/
public ApplicationDataSourceScriptDatabaseInitializer(DataSource dataSource,
SqlInitializationProperties properties) {
this(dataSource, ApplicationScriptDatabaseInitializer.getSettings(properties));
}
/**
* Create a new {@link ApplicationDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the primary SQL data source
* @param settings the database initialization settings
*/
public ApplicationDataSourceScriptDatabaseInitializer(DataSource dataSource,
DatabaseInitializationSettings settings) {
super(dataSource, settings);
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.metadata.autoconfigure.DataSourcePoolMetadataProvidersConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Olga Maciaszek-Sharma
* @since 4.0.0
*/
@AutoConfiguration(before = DataSourceInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
}
/**
* {@link AnyNestedCondition} that checks that either {@code spring.datasource.type}
* is set or {@link PooledDataSourceAvailableCondition} applies.
*/
static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty("spring.datasource.type")
static class ExplicitType {
}
@Conditional(PooledDataSourceAvailableCondition.class)
static class PooledDataSourceAvailable {
}
}
/**
* {@link Condition} to test if a supported connection pool is available.
*/
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("PooledDataSource");
if (DataSourceBuilder.findType(context.getClassLoader()) != null) {
return ConditionOutcome.match(message.foundExactly("supported DataSource"));
}
return ConditionOutcome.noMatch(message.didNotFind("supported DataSource").atAll());
}
}
/**
* {@link Condition} to detect when an embedded {@link DataSource} type can be used.
* If a pooled {@link DataSource} is available, it will always be preferred to an
* {@code EmbeddedDatabase}.
*/
static class EmbeddedDatabaseCondition extends SpringBootCondition {
private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";
private static final String EMBEDDED_DATABASE_TYPE = "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType";
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource");
if (hasDataSourceUrlProperty(context)) {
return ConditionOutcome.noMatch(message.because(DATASOURCE_URL_PROPERTY + " is set"));
}
if (anyMatches(context, metadata, this.pooledCondition)) {
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
}
if (!ClassUtils.isPresent(EMBEDDED_DATABASE_TYPE, context.getClassLoader())) {
return ConditionOutcome
.noMatch(message.didNotFind("required class").items(Style.QUOTE, EMBEDDED_DATABASE_TYPE));
}
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
if (type == null) {
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
}
return ConditionOutcome.match(message.found("embedded database").items(type));
}
private boolean hasDataSourceUrlProperty(ConditionContext context) {
Environment environment = context.getEnvironment();
if (environment.containsProperty(DATASOURCE_URL_PROPERTY)) {
try {
return StringUtils.hasText(environment.getProperty(DATASOURCE_URL_PROPERTY));
}
catch (IllegalArgumentException ex) {
// NOTE: This should be PlaceholderResolutionException
// Ignore unresolvable placeholder errors
}
}
return false;
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties.DataSourceBeanCreationException;
import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* An {@link AbstractFailureAnalyzer} for failures caused by a
* {@link DataSourceBeanCreationException}.
*
* @author Andy Wilkinson
* @author Patryk Kostrzewa
* @author Stephane Nicoll
*/
class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer<DataSourceBeanCreationException> {
private final Environment environment;
DataSourceBeanCreationFailureAnalyzer(Environment environment) {
this.environment = environment;
}
@Override
protected FailureAnalysis analyze(Throwable rootFailure, DataSourceBeanCreationException cause) {
return getFailureAnalysis(cause);
}
private FailureAnalysis getFailureAnalysis(DataSourceBeanCreationException cause) {
String description = getDescription(cause);
String action = getAction(cause);
return new FailureAnalysis(description, action, cause);
}
private String getDescription(DataSourceBeanCreationException cause) {
StringBuilder description = new StringBuilder();
description.append("Failed to configure a DataSource: ");
if (!StringUtils.hasText(cause.getProperties().getUrl())) {
description.append("'url' attribute is not specified and ");
}
description.append(String.format("no embedded datasource could be configured.%n"));
description.append(String.format("%nReason: %s%n", cause.getMessage()));
return description.toString();
}
private String getAction(DataSourceBeanCreationException cause) {
StringBuilder action = new StringBuilder();
action.append(String.format("Consider the following:%n"));
if (EmbeddedDatabaseConnection.NONE == cause.getConnection()) {
action.append(String
.format("\tIf you want an embedded database (H2, HSQL or Derby), please put it on the classpath.%n"));
}
else {
action.append(String.format("\tReview the configuration of %s%n.", cause.getConnection()));
}
action
.append("\tIf you have database settings to be loaded from a particular "
+ "profile you may need to activate it")
.append(getActiveProfiles());
return action.toString();
}
private String getActiveProfiles() {
StringBuilder message = new StringBuilder();
String[] profiles = this.environment.getActiveProfiles();
if (ObjectUtils.isEmpty(profiles)) {
message.append(" (no profiles are currently active).");
}
else {
message.append(" (the profiles ");
message.append(StringUtils.arrayToCommaDelimitedString(profiles));
message.append(" are currently active).");
}
return message.toString();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCheckpointRestore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Checkpoint-restore specific configuration.
*
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnCheckpointRestore
@ConditionalOnBean(DataSource.class)
class DataSourceCheckpointRestoreConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
static class Hikari {
@Bean
@ConditionalOnMissingBean
HikariCheckpointRestoreLifecycle hikariCheckpointRestoreLifecycle(DataSource dataSource,
ConfigurableApplicationContext applicationContext) {
return new HikariCheckpointRestoreLifecycle(dataSource, applicationContext);
}
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* Actual DataSource configurations imported by {@link DataSourceAutoConfiguration}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Fabio Grassi
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, 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) {
DataSourceBuilder<? extends DataSource> builder = DataSourceBuilder.create(classLoader).type(type);
if (applyDriverClassName) {
builder.driverClassName(connectionDetails.getDriverClassName());
}
return (T) builder.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.build();
}
/**
* Tomcat Pool DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
matchIfMissing = true)
static class Tomcat {
@Bean
@ConditionalOnMissingBean(PropertiesJdbcConnectionDetails.class)
static TomcatJdbcConnectionDetailsBeanPostProcessor tomcatJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new TomcatJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties("spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties,
JdbcConnectionDetails connectionDetails) {
Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(connectionDetails, dataSourceType,
properties.getClassLoader());
String validationQuery;
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(connectionDetails.getJdbcUrl());
validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
/**
* Hikari DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties("spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails,
Environment environment) {
String dataSourceClassName = environment.getProperty("spring.datasource.hikari.data-source-class-name");
HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
properties.getClassLoader(), dataSourceClassName == null);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
/**
* DBCP DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource",
matchIfMissing = true)
static class Dbcp2 {
@Bean
static Dbcp2JdbcConnectionDetailsBeanPostProcessor dbcp2JdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new Dbcp2JdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties("spring.datasource.dbcp2")
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties,
JdbcConnectionDetails connectionDetails) {
Class<? extends DataSource> dataSourceType = org.apache.commons.dbcp2.BasicDataSource.class;
return createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());
}
}
/**
* Oracle UCP DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class })
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
matchIfMissing = true)
static class OracleUcp {
@Bean
static OracleUcpJdbcConnectionDetailsBeanPostProcessor oracleUcpJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new OracleUcpJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties("spring.datasource.oracleucp")
PoolDataSourceImpl dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails)
throws SQLException {
PoolDataSourceImpl dataSource = createDataSource(connectionDetails, PoolDataSourceImpl.class,
properties.getClassLoader());
if (StringUtils.hasText(properties.getName())) {
dataSource.setConnectionPoolName(properties.getName());
}
return dataSource;
}
}
/**
* Generic DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
DataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
return createDataSource(connectionDetails, properties.getType(), properties.getClassLoader());
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.sql.init.ApplicationScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.sql.init.ConditionalOnSqlInitialization;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.util.StringUtils;
/**
* Auto-configuration for {@link DataSource} initialization.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnMissingBean(ApplicationScriptDatabaseInitializer.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass(DatabasePopulator.class)
@Import(DatabaseInitializationDependencyConfigurer.class)
@EnableConfigurationProperties(SqlInitializationProperties.class)
@ConditionalOnSqlInitialization
public class DataSourceInitializationAutoConfiguration {
@Bean
ApplicationDataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
SqlInitializationProperties properties) {
return new ApplicationDataSourceScriptDatabaseInitializer(
determineDataSource(dataSource, properties.getUsername(), properties.getPassword()), properties);
}
private static DataSource determineDataSource(DataSource dataSource, String username, String password) {
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
return DataSourceBuilder.derivedFrom(dataSource)
.username(username)
.password(password)
.type(SimpleDriverDataSource.class)
.build();
}
return dataSource;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
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.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.jdbc.DataSourceUnwrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
/**
* Configures DataSource related MBeans.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBooleanProperty("spring.jmx.enabled")
class DataSourceJmxConfiguration {
private static final Log logger = LogFactory.getLog(DataSourceJmxConfiguration.class);
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnSingleCandidate(DataSource.class)
static class Hikari {
private final DataSource dataSource;
private final ObjectProvider<MBeanExporter> mBeanExporter;
Hikari(DataSource dataSource, ObjectProvider<MBeanExporter> mBeanExporter) {
this.dataSource = dataSource;
this.mBeanExporter = mBeanExporter;
validateMBeans();
}
private void validateMBeans() {
HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(this.dataSource, HikariConfigMXBean.class,
HikariDataSource.class);
if (hikariDataSource != null && hikariDataSource.isRegisterMbeans()) {
this.mBeanExporter.ifUnique((exporter) -> exporter.addExcludedBean("dataSource"));
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBooleanProperty("spring.datasource.tomcat.jmx-enabled")
@ConditionalOnClass(DataSourceProxy.class)
@ConditionalOnSingleCandidate(DataSource.class)
static class TomcatDataSourceJmxConfiguration {
@Bean
@ConditionalOnMissingBean(name = "dataSourceMBean")
Object dataSourceMBean(DataSource dataSource) {
DataSourceProxy dataSourceProxy = DataSourceUnwrapper.unwrap(dataSource, PoolConfiguration.class,
DataSourceProxy.class);
if (dataSourceProxy != null) {
try {
return dataSourceProxy.createPool().getJmxPool();
}
catch (SQLException ex) {
logger.warn("Cannot expose DataSource to JMX (could not connect)");
}
}
return null;
}
}
}

View File

@@ -0,0 +1,404 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import javax.sql.DataSource;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Base class for configuration of a data source.
*
* @author Dave Syer
* @author Maciej Walkowiak
* @author Stephane Nicoll
* @author Benedikt Ritter
* @author Eddú Meléndez
* @author Scott Frederick
* @since 4.0.0
*/
@ConfigurationProperties("spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
/**
* Whether to generate a random datasource name.
*/
private boolean generateUniqueName = true;
/**
* Datasource name to use if "generate-unique-name" is false. Defaults to "testdb"
* when using an embedded database, otherwise null.
*/
private 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;
/**
* Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
*/
private String driverClassName;
/**
* JDBC URL of the database.
*/
private String url;
/**
* Login username of the database.
*/
private String username;
/**
* Login password of the database.
*/
private String password;
/**
* JNDI location of the datasource. Class, url, username and password are ignored when
* set.
*/
private String jndiName;
/**
* Connection details for an embedded database. Defaults to the most suitable embedded
* database that is available on the classpath.
*/
private EmbeddedDatabaseConnection embeddedDatabaseConnection;
private Xa xa = new Xa();
private String uniqueName;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.embeddedDatabaseConnection == null) {
this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
}
}
/**
* Initialize a {@link DataSourceBuilder} with the state of this instance.
* @return a {@link DataSourceBuilder} initialized with the customizations defined on
* this instance
*/
public DataSourceBuilder<?> initializeDataSourceBuilder() {
return DataSourceBuilder.create(getClassLoader())
.type(getType())
.driverClassName(determineDriverClassName())
.url(determineUrl())
.username(determineUsername())
.password(determinePassword());
}
public boolean isGenerateUniqueName() {
return this.generateUniqueName;
}
public void setGenerateUniqueName(boolean generateUniqueName) {
this.generateUniqueName = generateUniqueName;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Class<? extends DataSource> getType() {
return this.type;
}
public void setType(Class<? extends DataSource> type) {
this.type = type;
}
/**
* Return the configured driver or {@code null} if none was configured.
* @return the configured driver
* @see #determineDriverClassName()
*/
public String getDriverClassName() {
return this.driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
/**
* Determine the driver to use based on this configuration and the environment.
* @return the driver to use
*/
public String determineDriverClassName() {
String driverClassName = findDriverClassName();
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName;
}
String findDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
return driverClassName;
}
private boolean driverClassIsLoadable() {
try {
ClassUtils.forName(this.driverClassName, null);
return true;
}
catch (UnsupportedClassVersionError ex) {
// Driver library has been compiled with a later JDK, propagate error
throw ex;
}
catch (Throwable ex) {
return false;
}
}
/**
* Return the configured url or {@code null} if none was configured.
* @return the configured url
* @see #determineUrl()
*/
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
/**
* Determine the url to use based on this configuration and the environment.
* @return the url to use
*/
public String determineUrl() {
if (StringUtils.hasText(this.url)) {
return this.url;
}
String databaseName = determineDatabaseName();
String url = (databaseName != null) ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
if (!StringUtils.hasText(url)) {
throw new DataSourceBeanCreationException("Failed to determine suitable jdbc url", this,
this.embeddedDatabaseConnection);
}
return url;
}
/**
* Determine the name to used based on this configuration.
* @return the database name to use or {@code null}
*/
public String determineDatabaseName() {
if (this.generateUniqueName) {
if (this.uniqueName == null) {
this.uniqueName = UUID.randomUUID().toString();
}
return this.uniqueName;
}
if (StringUtils.hasLength(this.name)) {
return this.name;
}
if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) {
return "testdb";
}
return null;
}
/**
* Return the configured username or {@code null} if none was configured.
* @return the configured username
* @see #determineUsername()
*/
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
/**
* Determine the username to use based on this configuration and the environment.
* @return the username to use
*/
public String determineUsername() {
if (StringUtils.hasText(this.username)) {
return this.username;
}
if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return "sa";
}
return null;
}
/**
* Return the configured password or {@code null} if none was configured.
* @return the configured password
* @see #determinePassword()
*/
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* Determine the password to use based on this configuration and the environment.
* @return the password to use
*/
public String determinePassword() {
if (StringUtils.hasText(this.password)) {
return this.password;
}
if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return "";
}
return null;
}
public String getJndiName() {
return this.jndiName;
}
/**
* Allows the DataSource to be managed by the container and obtained through JNDI. The
* {@code URL}, {@code driverClassName}, {@code username} and {@code password} fields
* will be ignored when using JNDI lookups.
* @param jndiName the JNDI name
*/
public void setJndiName(String jndiName) {
this.jndiName = jndiName;
}
public EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
return this.embeddedDatabaseConnection;
}
public void setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection embeddedDatabaseConnection) {
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
}
public ClassLoader getClassLoader() {
return this.classLoader;
}
public Xa getXa() {
return this.xa;
}
public void setXa(Xa xa) {
this.xa = xa;
}
/**
* XA Specific datasource settings.
*/
public static class Xa {
/**
* XA datasource fully qualified name.
*/
private String dataSourceClassName;
/**
* Properties to pass to the XA data source.
*/
private Map<String, String> properties = new LinkedHashMap<>();
public String getDataSourceClassName() {
return this.dataSourceClassName;
}
public void setDataSourceClassName(String dataSourceClassName) {
this.dataSourceClassName = dataSourceClassName;
}
public Map<String, String> getProperties() {
return this.properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
}
static class DataSourceBeanCreationException extends BeanCreationException {
private final DataSourceProperties properties;
private final EmbeddedDatabaseConnection connection;
DataSourceBeanCreationException(String message, DataSourceProperties properties,
EmbeddedDatabaseConnection connection) {
super(message);
this.properties = properties;
this.connection = connection;
}
DataSourceProperties getProperties() {
return this.properties;
}
EmbeddedDatabaseConnection getConnection() {
return this.connection;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.TransactionManager;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link JdbcTransactionManager}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Kazuki Shimizu
* @since 4.0.0
*/
@AutoConfiguration(before = TransactionAutoConfiguration.class,
after = { DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class })
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class, TransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class DataSourceTransactionManagerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(DataSource.class)
static class JdbcTransactionManagerConfiguration {
@Bean
@ConditionalOnMissingBean(TransactionManager.class)
DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link BasicDataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<BasicDataSource> {
Dbcp2JdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(BasicDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(BasicDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
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;
/**
* Configuration for embedded data sources.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 4.0.0
* @see DataSourceAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DataSourceProperties.class)
public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Bean(destroyMethod = "shutdown")
public EmbeddedDatabase dataSource(DataSourceProperties properties) {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.get(this.classLoader).getType())
.setName(properties.determineDatabaseName())
.build();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
/**
* An {@link AbstractFailureAnalyzer} that performs analysis of a Hikari configuration
* failure caused by the use of the unsupported 'dataSourceClassName' property.
*
* @author Stephane Nicoll
*/
class HikariDriverConfigurationFailureAnalyzer extends AbstractFailureAnalyzer<CannotGetJdbcConnectionException> {
private static final String EXPECTED_MESSAGE = "cannot use driverClassName and dataSourceClassName together.";
@Override
protected FailureAnalysis analyze(Throwable rootFailure, CannotGetJdbcConnectionException cause) {
Throwable subCause = cause.getCause();
if (subCause == null || !EXPECTED_MESSAGE.equals(subCause.getMessage())) {
return null;
}
return new FailureAnalysis(
"Configuration of the Hikari connection pool failed: 'dataSourceClassName' is not supported.",
"Spring Boot auto-configures only a driver and can't specify a custom "
+ "DataSource. Consider configuring the Hikari DataSource in your own configuration.",
cause);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link HikariDataSource} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<HikariDataSource> {
HikariJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(HikariDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(HikariDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setJdbcUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
if (driverClassName != null) {
dataSource.setDriverClassName(driverClassName);
}
return dataSource;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.JdbcClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link JdbcClient}.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@AutoConfiguration(after = JdbcTemplateAutoConfiguration.class)
@ConditionalOnSingleCandidate(NamedParameterJdbcTemplate.class)
@ConditionalOnMissingBean(JdbcClient.class)
@Import(DatabaseInitializationDependencyConfigurer.class)
public class JdbcClientAutoConfiguration {
@Bean
JdbcClient jdbcClient(NamedParameterJdbcTemplate jdbcTemplate) {
return JdbcClient.create(jdbcTemplate);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* Details required to establish a connection to an SQL service using JDBC.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface JdbcConnectionDetails extends ConnectionDetails {
/**
* Username for the database.
* @return the username for the database
*/
String getUsername();
/**
* Password for the database.
* @return the password for the database
*/
String getPassword();
/**
* JDBC url for the database.
* @return the JDBC url for the database
*/
String getJdbcUrl();
/**
* The name of the JDBC driver class. Defaults to the class name of the driver
* specified in the JDBC URL.
* @return the JDBC driver class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getDriverClassName()
*/
default String getDriverClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
}
/**
* Returns the name of the XA DataSource class. Defaults to the class name from the
* driver specified in the JDBC URL.
* @return the XA DataSource class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getXaDataSourceClassName()
*/
default String getXaDataSourceClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getXaDataSourceClassName();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
/**
* Abstract base class for DataSource bean post processors which apply values from
* {@link JdbcConnectionDetails}. Property-based connection details
* ({@link PropertiesJdbcConnectionDetails} are ignored as the expectation is that they
* will have already been applied by configuration property binding. Acts on beans named
* 'dataSource' of type {@code T}.
*
* @param <T> type of the datasource
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class JdbcConnectionDetailsBeanPostProcessor<T> implements BeanPostProcessor, PriorityOrdered {
private final Class<T> dataSourceClass;
private final ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider;
JdbcConnectionDetailsBeanPostProcessor(Class<T> dataSourceClass,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
this.dataSourceClass = dataSourceClass;
this.connectionDetailsProvider = connectionDetailsProvider;
}
@Override
@SuppressWarnings("unchecked")
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (this.dataSourceClass.isAssignableFrom(bean.getClass()) && "dataSource".equals(beanName)) {
JdbcConnectionDetails connectionDetails = this.connectionDetailsProvider.getObject();
if (!(connectionDetails instanceof PropertiesJdbcConnectionDetails)) {
return processDataSource((T) bean, connectionDetails);
}
}
return bean;
}
protected abstract Object processDataSource(T dataSource, JdbcConnectionDetails connectionDetails);
@Override
public int getOrder() {
// Runs after ConfigurationPropertiesBindingPostProcessor
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
/**
* Configuration properties for JDBC.
*
* @author Kazuki Shimizu
* @author Stephane Nicoll
* @since 2.0.0
*/
@ConfigurationProperties("spring.jdbc")
public class JdbcProperties {
private final Template template = new Template();
public Template getTemplate() {
return this.template;
}
/**
* {@code JdbcTemplate} settings.
*/
public static class Template {
/**
* Whether to ignore JDBC statement warnings (SQLWarning). When set to false,
* throw an SQLWarningException instead.
*/
private boolean ignoreWarnings = true;
/**
* Number of rows that should be fetched from the database when more rows are
* needed. Use -1 to use the JDBC driver's default configuration.
*/
private int fetchSize = -1;
/**
* Maximum number of rows. Use -1 to use the JDBC driver's default configuration.
*/
private int maxRows = -1;
/**
* Query timeout. Default is to use the JDBC driver's default configuration. If a
* duration suffix is not specified, seconds will be used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration queryTimeout;
/**
* Whether results processing should be skipped. Can be used to optimize callable
* statement processing when we know that no results are being passed back.
*/
private boolean skipResultsProcessing;
/**
* Whether undeclared results should be skipped.
*/
private boolean skipUndeclaredResults;
/**
* Whether execution of a CallableStatement will return the results in a Map that
* uses case-insensitive names for the parameters.
*/
private boolean resultsMapCaseInsensitive;
public boolean isIgnoreWarnings() {
return this.ignoreWarnings;
}
public void setIgnoreWarnings(boolean ignoreWarnings) {
this.ignoreWarnings = ignoreWarnings;
}
public int getFetchSize() {
return this.fetchSize;
}
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
public int getMaxRows() {
return this.maxRows;
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public Duration getQueryTimeout() {
return this.queryTimeout;
}
public void setQueryTimeout(Duration queryTimeout) {
this.queryTimeout = queryTimeout;
}
public boolean isSkipResultsProcessing() {
return this.skipResultsProcessing;
}
public void setSkipResultsProcessing(boolean skipResultsProcessing) {
this.skipResultsProcessing = skipResultsProcessing;
}
public boolean isSkipUndeclaredResults() {
return this.skipUndeclaredResults;
}
public void setSkipUndeclaredResults(boolean skipUndeclaredResults) {
this.skipUndeclaredResults = skipUndeclaredResults;
}
public boolean isResultsMapCaseInsensitive() {
return this.resultsMapCaseInsensitive;
}
public void setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) {
this.resultsMapCaseInsensitive = resultsMapCaseInsensitive;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link JdbcTemplate} and
* {@link NamedParameterJdbcTemplate}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @since 1.4.0
*/
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.SQLExceptionTranslator;
/**
* Configuration for {@link JdbcTemplateConfiguration}.
*
* @author Stephane Nicoll
* @author Yanming Zhou
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(JdbcOperations.class)
class JdbcTemplateConfiguration {
@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties,
ObjectProvider<SQLExceptionTranslator> sqlExceptionTranslator) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
JdbcProperties.Template template = properties.getTemplate();
jdbcTemplate.setIgnoreWarnings(template.isIgnoreWarnings());
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
jdbcTemplate.setSkipResultsProcessing(template.isSkipResultsProcessing());
jdbcTemplate.setSkipUndeclaredResults(template.isSkipUndeclaredResults());
jdbcTemplate.setResultsMapCaseInsensitive(template.isResultsMapCaseInsensitive());
sqlExceptionTranslator.ifUnique(jdbcTemplate::setExceptionTranslator);
return jdbcTemplate;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for a JNDI located
* {@link DataSource}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration(before = { XADataSourceAutoConfiguration.class, DataSourceAutoConfiguration.class })
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnProperty("spring.datasource.jndi-name")
@EnableConfigurationProperties(DataSourceProperties.class)
public class JndiDataSourceAutoConfiguration {
@Bean(destroyMethod = "")
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties, ApplicationContext context) {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource(properties.getJndiName());
excludeMBeanIfNecessary(dataSource, "dataSource", context);
return dataSource;
}
private void excludeMBeanIfNecessary(Object candidate, String beanName, ApplicationContext context) {
for (MBeanExporter mbeanExporter : context.getBeansOfType(MBeanExporter.class).values()) {
if (JmxUtils.isMBean(candidate.getClass())) {
mbeanExporter.addExcludedBean(beanName);
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
/**
* Configuration for {@link NamedParameterJdbcTemplate}.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(JdbcTemplate.class)
@ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
class NamedParameterJdbcTemplateConfiguration {
@Bean
@Primary
NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link PoolDataSourceImpl} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessor
extends JdbcConnectionDetailsBeanPostProcessor<PoolDataSourceImpl> {
OracleUcpJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(PoolDataSourceImpl.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(PoolDataSourceImpl dataSource, JdbcConnectionDetails connectionDetails) {
try {
dataSource.setURL(connectionDetails.getJdbcUrl());
dataSource.setUser(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setConnectionFactoryClassName(connectionDetails.getDriverClassName());
return dataSource;
}
catch (SQLException ex) {
throw new RuntimeException("Failed to set URL / user / password of datasource", ex);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
/**
* Adapts {@link DataSourceProperties} to {@link JdbcConnectionDetails}.
*
* @author Andy Wilkinson
*/
final class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final DataSourceProperties properties;
PropertiesJdbcConnectionDetails(DataSourceProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
return this.properties.determinePassword();
}
@Override
public String getJdbcUrl() {
return this.properties.determineUrl();
}
@Override
public String getDriverClassName() {
return this.properties.determineDriverClassName();
}
@Override
public String getXaDataSourceClassName() {
return (this.properties.getXa().getDataSourceClassName() != null)
? this.properties.getXa().getDataSourceClassName()
: JdbcConnectionDetails.super.getXaDataSourceClassName();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link DataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<DataSource> {
TomcatJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(DataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(DataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import jakarta.transaction.TransactionManager;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties.DataSourceBeanCreationException;
import org.springframework.boot.transaction.jta.autoconfigure.JtaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource} with XA.
*
* @author Phillip Webb
* @author Josh Long
* @author Madhura Bhave
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 4.0.0
*/
@AutoConfiguration(before = DataSourceAutoConfiguration.class, after = JtaAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceProperties.class)
@ConditionalOnClass({ DataSource.class, TransactionManager.class, EmbeddedDatabaseType.class })
@ConditionalOnBean(XADataSourceWrapper.class)
@ConditionalOnMissingBean(DataSource.class)
public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Bean
@ConditionalOnMissingBean(JdbcConnectionDetails.class)
PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
return new PropertiesJdbcConnectionDetails(properties);
}
@Bean
public DataSource dataSource(XADataSourceWrapper wrapper, DataSourceProperties properties,
JdbcConnectionDetails connectionDetails, ObjectProvider<XADataSource> xaDataSource) throws Exception {
return wrapper
.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties, connectionDetails)));
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
private XADataSource createXaDataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
String className = connectionDetails.getXaDataSourceClassName();
Assert.state(StringUtils.hasLength(className), "No XA DataSource class name specified");
XADataSource dataSource = createXaDataSourceInstance(className);
bindXaProperties(dataSource, properties, connectionDetails);
return dataSource;
}
private XADataSource createXaDataSourceInstance(String className) {
try {
Class<?> dataSourceClass = ClassUtils.forName(className, this.classLoader);
Object instance = BeanUtils.instantiateClass(dataSourceClass);
Assert.state(instance instanceof XADataSource,
() -> "DataSource class " + className + " is not an XADataSource");
return (XADataSource) instance;
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create XADataSource instance from '" + className + "'");
}
}
private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Binder binder = new Binder(getBinderSource(dataSourceProperties, connectionDetails));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
}
private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Map<Object, Object> properties = new HashMap<>(dataSourceProperties.getXa().getProperties());
properties.computeIfAbsent("user", (key) -> connectionDetails.getUsername());
properties.computeIfAbsent("password", (key) -> connectionDetails.getPassword());
try {
properties.computeIfAbsent("url", (key) -> connectionDetails.getJdbcUrl());
}
catch (DataSourceBeanCreationException ex) {
// Continue as not all XA DataSource's require a URL
}
MapConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAliases("user", "username");
return source.withAliases(aliases);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for JDBC.
*/
package org.springframework.boot.jdbc.autoconfigure;

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
/**
* {@link InitializingBean} that performs {@link DataSource} initialization using schema
* (DDL) and data (DML) scripts.
*
* @author Andy Wilkinson
* @since 2.5.0
*/
public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer {
private static final Log logger = LogFactory.getLog(DataSourceScriptDatabaseInitializer.class);
private final DataSource dataSource;
/**
* Creates a new {@link DataSourceScriptDatabaseInitializer} that will initialize the
* given {@code DataSource} using the given settings.
* @param dataSource data source to initialize
* @param settings the initialization settings
*/
public DataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) {
super(settings);
this.dataSource = dataSource;
}
/**
* Returns the {@code DataSource} that will be initialized.
* @return the initialization data source
*/
protected final DataSource getDataSource() {
return this.dataSource;
}
@Override
protected boolean isEmbeddedDatabase() {
try {
return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
}
catch (Exception ex) {
logger.debug("Could not determine if datasource is embedded", ex);
return false;
}
}
@Override
protected void runScripts(Scripts scripts) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(scripts.isContinueOnError());
populator.setSeparator(scripts.getSeparator());
if (scripts.getEncoding() != null) {
populator.setSqlScriptEncoding(scripts.getEncoding().name());
}
for (Resource resource : scripts) {
populator.addScript(resource);
}
customize(populator);
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
/**
* Customize the {@link ResourceDatabasePopulator}.
* @param populator the configured database populator
* @since 2.6.2
*/
protected void customize(ResourceDatabasePopulator populator) {
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Collections;
import java.util.Set;
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDatabaseInitializerDetector;
import org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector;
import org.springframework.core.Ordered;
/**
* A {@link DatabaseInitializerDetector} for {@link DataSourceScriptDatabaseInitializer}.
*
* @author Andy Wilkinson
*/
class DataSourceScriptDatabaseInitializerDetector extends AbstractBeansOfTypeDatabaseInitializerDetector {
static final int PRECEDENCE = Ordered.LOWEST_PRECEDENCE - 100;
@Override
protected Set<Class<?>> getDatabaseInitializerBeanTypes() {
return Collections.singleton(DataSourceScriptDatabaseInitializer.class);
}
@Override
public int getOrder() {
return PRECEDENCE;
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Utility class that can resolve placeholder text with the actual {@link DatabaseDriver}
* platform.
* <p>
* By default, the name of the platform is the {@link DatabaseDriver#getId ID of the
* driver}. This mapping can be customized by
* {@link #withDriverPlatform(DatabaseDriver, String)} registering custom
* {@code DatabaseDriver} to platform mappings.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 2.6.0
*/
public class PlatformPlaceholderDatabaseDriverResolver {
private final String placeholder;
private final Map<DatabaseDriver, String> driverMappings;
/**
* Creates a new resolver that will use the default {@code "@@platform@@"}
* placeholder.
*/
public PlatformPlaceholderDatabaseDriverResolver() {
this("@@platform@@");
}
/**
* Creates a new resolver that will use the given {@code placeholder}.
* @param placeholder the placeholder to use
*/
public PlatformPlaceholderDatabaseDriverResolver(String placeholder) {
this(placeholder, Collections.emptyMap());
}
private PlatformPlaceholderDatabaseDriverResolver(String placeholder, Map<DatabaseDriver, String> driverMappings) {
this.placeholder = placeholder;
this.driverMappings = driverMappings;
}
/**
* Creates a new {@link PlatformPlaceholderDatabaseDriverResolver} that will map the
* given {@code driver} to the given {@code platform}.
* @param driver the driver
* @param platform the platform
* @return the new resolver
*/
public PlatformPlaceholderDatabaseDriverResolver withDriverPlatform(DatabaseDriver driver, String platform) {
Map<DatabaseDriver, String> driverMappings = new LinkedHashMap<>(this.driverMappings);
driverMappings.put(driver, platform);
return new PlatformPlaceholderDatabaseDriverResolver(this.placeholder, driverMappings);
}
/**
* Resolves the placeholders in the given {@code values}, replacing them with the
* platform derived from the {@link DatabaseDriver} of the given {@code dataSource}.
* @param dataSource the DataSource from which the {@link DatabaseDriver} is derived
* @param values the values in which placeholders are resolved
* @return the values with their placeholders resolved
*/
public List<String> resolveAll(DataSource dataSource, String... values) {
Assert.notNull(dataSource, "'dataSource' must not be null");
return resolveAll(() -> determinePlatform(dataSource), values);
}
/**
* Resolves the placeholders in the given {@code values}, replacing them with the
* given platform.
* @param platform the platform to use
* @param values the values in which placeholders are resolved
* @return the values with their placeholders resolved
* @since 2.6.2
*/
public List<String> resolveAll(String platform, String... values) {
Assert.notNull(platform, "'platform' must not be null");
return resolveAll(() -> platform, values);
}
private List<String> resolveAll(Supplier<String> platformProvider, String... values) {
if (ObjectUtils.isEmpty(values)) {
return Collections.emptyList();
}
List<String> resolved = new ArrayList<>(values.length);
String platform = null;
for (String value : values) {
if (StringUtils.hasLength(value)) {
if (value.contains(this.placeholder)) {
platform = (platform != null) ? platform : platformProvider.get();
value = value.replace(this.placeholder, platform);
}
}
resolved.add(value);
}
return Collections.unmodifiableList(resolved);
}
private String determinePlatform(DataSource dataSource) {
DatabaseDriver databaseDriver = getDatabaseDriver(dataSource);
Assert.state(databaseDriver != DatabaseDriver.UNKNOWN, "Unable to detect database type");
return this.driverMappings.getOrDefault(databaseDriver, databaseDriver.getId());
}
DatabaseDriver getDatabaseDriver(DataSource dataSource) {
try {
String productName = JdbcUtils.commonDatabaseName(
JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getDatabaseProductName));
return DatabaseDriver.fromProductName(productName);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to determine DatabaseDriver", ex);
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for initialization of an SQL database using a JDBC {@link javax.sql.DataSource
* DataSource}.
*/
package org.springframework.boot.jdbc.init;

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
/**
* A base {@link DataSourcePoolMetadata} implementation.
*
* @param <T> the data source type
* @author Stephane Nicoll
* @since 2.0.0
*/
public abstract class AbstractDataSourcePoolMetadata<T extends DataSource> implements DataSourcePoolMetadata {
private final T dataSource;
/**
* Create an instance with the data source to use.
* @param dataSource the data source
*/
protected AbstractDataSourcePoolMetadata(T dataSource) {
this.dataSource = dataSource;
}
@Override
public Float getUsage() {
Integer maxSize = getMax();
Integer currentSize = getActive();
if (maxSize == null || currentSize == null) {
return null;
}
if (maxSize < 0) {
return -1f;
}
if (currentSize == 0) {
return 0f;
}
return (float) currentSize / (float) maxSize;
}
protected final T getDataSource() {
return this.dataSource;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
/**
* {@link DataSourcePoolMetadata} for an Apache Commons DBCP2 {@link DataSource}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class CommonsDbcp2DataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<BasicDataSource> {
public CommonsDbcp2DataSourcePoolMetadata(BasicDataSource dataSource) {
super(dataSource);
}
@Override
public Integer getActive() {
return getDataSource().getNumActive();
}
@Override
public Integer getIdle() {
return getDataSource().getNumIdle();
}
@Override
public Integer getMax() {
return getDataSource().getMaxTotal();
}
@Override
public Integer getMin() {
return getDataSource().getMinIdle();
}
@Override
public String getValidationQuery() {
return getDataSource().getValidationQuery();
}
@Override
public Boolean getDefaultAutoCommit() {
return getDataSource().getDefaultAutoCommit();
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
/**
* A {@link DataSourcePoolMetadataProvider} implementation that returns the first
* {@link DataSourcePoolMetadata} that is found by one of its delegate.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class CompositeDataSourcePoolMetadataProvider implements DataSourcePoolMetadataProvider {
private final List<DataSourcePoolMetadataProvider> providers;
/**
* Create a {@link CompositeDataSourcePoolMetadataProvider} instance with an initial
* collection of delegates to use.
* @param providers the data source pool metadata providers
*/
public CompositeDataSourcePoolMetadataProvider(Collection<? extends DataSourcePoolMetadataProvider> providers) {
this.providers = (providers != null) ? List.copyOf(providers) : Collections.emptyList();
}
@Override
public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
for (DataSourcePoolMetadataProvider provider : this.providers) {
DataSourcePoolMetadata metadata = provider.getDataSourcePoolMetadata(dataSource);
if (metadata != null) {
return metadata;
}
}
return null;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
/**
* Provides access meta-data that is commonly available from most pooled
* {@link DataSource} implementations.
*
* @author Stephane Nicoll
* @author Artsiom Yudovin
* @since 2.0.0
*/
public interface DataSourcePoolMetadata {
/**
* Return the usage of the pool as value between 0 and 1 (or -1 if the pool is not
* limited).
* <ul>
* <li>1 means that the maximum number of connections have been allocated</li>
* <li>0 means that no connection is currently active</li>
* <li>-1 means there is not limit to the number of connections that can be allocated
* </li>
* </ul>
* This may also return {@code null} if the data source does not provide the necessary
* information to compute the poll usage.
* @return the usage value or {@code null}
*/
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();
/**
* Return the number of established but idle connections. Can also return {@code null}
* if that information is not available.
* @return the number of established but idle connections or {@code null}
* @since 2.2.0
* @see #getActive()
*/
default Integer getIdle() {
return null;
}
/**
* Return the maximum number of active connections that can be allocated at the same
* time or {@code -1} if there is no limit. Can also return {@code null} if that
* information is not available.
* @return the maximum number of active connections or {@code null}
*/
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();
/**
* 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();
/**
* The default auto-commit state of connections created by this pool. If not set
* ({@code null}), default is JDBC driver default (If set to null then the
* java.sql.Connection.setAutoCommit(boolean) method will not be called.)
* @return the default auto-commit state or {@code null}
*/
Boolean getDefaultAutoCommit();
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
/**
* Provide a {@link DataSourcePoolMetadata} based on a {@link DataSource}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@FunctionalInterface
public interface DataSourcePoolMetadataProvider {
/**
* Return the {@link DataSourcePoolMetadata} instance able to manage the specified
* {@link DataSource} or {@code null} if the given data source could not be handled.
* @param dataSource the data source
* @return the data source pool metadata
*/
DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource);
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool;
import org.springframework.beans.DirectFieldAccessor;
/**
* {@link DataSourcePoolMetadata} for a Hikari {@link DataSource}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class HikariDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<HikariDataSource> {
public HikariDataSourcePoolMetadata(HikariDataSource dataSource) {
super(dataSource);
}
@Override
public Integer getActive() {
try {
return getHikariPool().getActiveConnections();
}
catch (Exception ex) {
return null;
}
}
@Override
public Integer getIdle() {
try {
return getHikariPool().getIdleConnections();
}
catch (Exception ex) {
return null;
}
}
private HikariPool getHikariPool() {
return (HikariPool) new DirectFieldAccessor(getDataSource()).getPropertyValue("pool");
}
@Override
public Integer getMax() {
return getDataSource().getMaximumPoolSize();
}
@Override
public Integer getMin() {
return getDataSource().getMinimumIdle();
}
@Override
public String getValidationQuery() {
return getDataSource().getConnectionTestQuery();
}
@Override
public Boolean getDefaultAutoCommit() {
return getDataSource().isAutoCommit();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import java.sql.SQLException;
import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import org.springframework.util.StringUtils;
/**
* {@link DataSourcePoolMetadata} for an Oracle UCP {@link DataSource}.
*
* @author Fabio Grassi
* @since 2.4.0
*/
public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<PoolDataSource> {
public OracleUcpDataSourcePoolMetadata(PoolDataSource dataSource) {
super(dataSource);
}
@Override
public Integer getActive() {
try {
return getDataSource().getBorrowedConnectionsCount();
}
catch (SQLException ex) {
return null;
}
}
@Override
public Integer getIdle() {
try {
return getDataSource().getAvailableConnectionsCount();
}
catch (SQLException ex) {
return null;
}
}
@Override
public Integer getMax() {
return getDataSource().getMaxPoolSize();
}
@Override
public Integer getMin() {
return getDataSource().getMinPoolSize();
}
@Override
public String getValidationQuery() {
return getDataSource().getSQLForValidateConnection();
}
@Override
public Boolean getDefaultAutoCommit() {
String autoCommit = getDataSource().getConnectionProperty("autoCommit");
return StringUtils.hasText(autoCommit) ? Boolean.valueOf(autoCommit) : null;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.DataSource;
/**
* {@link DataSourcePoolMetadata} for a Tomcat DataSource.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class TomcatDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<DataSource> {
public TomcatDataSourcePoolMetadata(DataSource dataSource) {
super(dataSource);
}
@Override
public Integer getActive() {
ConnectionPool pool = getDataSource().getPool();
return (pool != null) ? pool.getActive() : 0;
}
@Override
public Integer getIdle() {
return getDataSource().getNumIdle();
}
@Override
public Integer getMax() {
return getDataSource().getMaxActive();
}
@Override
public Integer getMin() {
return getDataSource().getMinIdle();
}
@Override
public String getValidationQuery() {
return getDataSource().getValidationQuery();
}
@Override
public Boolean getDefaultAutoCommit() {
return getDataSource().isDefaultAutoCommit();
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata.autoconfigure;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.OracleConnection;
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.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.jdbc.DataSourceUnwrapper;
import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
import org.springframework.boot.jdbc.metadata.OracleUcpDataSourcePoolMetadata;
import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Register the {@link DataSourcePoolMetadataProvider} instances for the supported data
* sources.
*
* @author Stephane Nicoll
* @author Fabio Grassi
* @since 1.2.0
*/
@Configuration(proxyBeanMethods = false)
public class DataSourcePoolMetadataProvidersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
static class TomcatDataSourcePoolMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
return (dataSource) -> {
org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = DataSourceUnwrapper.unwrap(dataSource,
ConnectionPoolMBean.class, org.apache.tomcat.jdbc.pool.DataSource.class);
if (tomcatDataSource != null) {
return new TomcatDataSourcePoolMetadata(tomcatDataSource);
}
return null;
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
static class HikariPoolDataSourceMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
return (dataSource) -> {
HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class,
HikariDataSource.class);
if (hikariDataSource != null) {
return new HikariDataSourcePoolMetadata(hikariDataSource);
}
return null;
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(BasicDataSource.class)
static class CommonsDbcp2PoolDataSourceMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {
return (dataSource) -> {
BasicDataSource dbcpDataSource = DataSourceUnwrapper.unwrap(dataSource, BasicDataSourceMXBean.class,
BasicDataSource.class);
if (dbcpDataSource != null) {
return new CommonsDbcp2DataSourcePoolMetadata(dbcpDataSource);
}
return null;
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ PoolDataSource.class, OracleConnection.class })
static class OracleUcpPoolDataSourceMetadataProviderConfiguration {
@Bean
DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() {
return (dataSource) -> {
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
if (ucpDataSource != null) {
return new OracleUcpDataSourcePoolMetadata(ucpDataSource);
}
return null;
};
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for JDBC Metadata.
*/
package org.springframework.boot.jdbc.metadata.autoconfigure;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for accessing JDBC {@link javax.sql.DataSource} metadata.
*/
package org.springframework.boot.jdbc.metadata;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for Java Database Connectivity (JDBC).
*/
package org.springframework.boot.jdbc;

View File

@@ -0,0 +1,184 @@
{
"properties": [
{
"name": "spring.datasource.continue-on-error",
"type": "java.lang.Boolean",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.continue-on-error"
}
},
{
"name": "spring.datasource.data",
"type": "java.util.List<java.lang.String>",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.data-locations"
}
},
{
"name": "spring.datasource.data-password",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.password"
}
},
{
"name": "spring.datasource.data-username",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.username"
}
},
{
"name": "spring.datasource.initialization-mode",
"type": "org.springframework.boot.jdbc.DataSourceInitializationMode",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.mode"
}
},
{
"name": "spring.datasource.jmx-enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable JMX support (if provided by the underlying pool).",
"defaultValue": false,
"deprecation": {
"level": "error",
"replacement": "spring.datasource.tomcat.jmx-enabled"
}
},
{
"name": "spring.datasource.platform",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.platform"
}
},
{
"name": "spring.datasource.schema",
"type": "java.util.List<java.lang.String>",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.schema-locations"
}
},
{
"name": "spring.datasource.schema-password",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.password"
}
},
{
"name": "spring.datasource.schema-username",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.username"
}
},
{
"name": "spring.datasource.separator",
"type": "java.lang.String",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.separator"
}
},
{
"name": "spring.datasource.sql-script-encoding",
"type": "java.nio.charset.Charset",
"deprecation": {
"level": "error",
"replacement": "spring.sql.init.encoding"
}
}
],
"hints": [
{
"name": "spring.datasource.data",
"providers": [
{
"name": "handle-as",
"parameters": {
"target": "java.util.List<org.springframework.core.io.Resource>"
}
}
]
},
{
"name": "spring.datasource.driver-class-name",
"providers": [
{
"name": "class-reference",
"parameters": {
"target": "java.sql.Driver"
}
}
]
},
{
"name": "spring.datasource.schema",
"providers": [
{
"name": "handle-as",
"parameters": {
"target": "java.util.List<org.springframework.core.io.Resource>"
}
}
]
},
{
"name": "spring.datasource.xa.data-source-class-name",
"providers": [
{
"name": "class-reference",
"parameters": {
"target": "javax.sql.XADataSource"
}
}
]
},
{
"name": "spring.datasource.xa.data-source-class-name",
"providers": [
{
"name": "class-reference",
"parameters": {
"target": "javax.sql.XADataSource"
}
}
]
}
],
"ignored": {
"properties": [
{
"name": "spring.datasource.dbcp2.driver"
},
{
"name": "spring.datasource.hikari.credentials"
},
{
"name": "spring.datasource.hikari.exception-override"
},
{
"name": "spring.datasource.hikari.metrics-tracker-factory"
},
{
"name": "spring.datasource.hikari.scheduled-executor"
},
{
"name": "spring.datasource.oracleucp.connection-wait-duration-in-millis"
},
{
"name": "spring.datasource.oracleucp.hostname-resolver"
}
]
}
}

View File

@@ -0,0 +1,12 @@
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.jdbc.autoconfigure.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.jdbc.autoconfigure.HikariDriverConfigurationFailureAnalyzer
# Database Initializer Detectors
org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector=\
org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector
# Depends On Database Initialization Detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector

View File

@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.jdbc.DataSourceBuilderRuntimeHints

View File

@@ -0,0 +1,7 @@
org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.JdbcClientAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.JndiDataSourceAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.XADataSourceAutoConfiguration

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceBuilder} when Hikari is not on the classpath.
*
* @author Phillip Webb
*/
@ClassPathExclusions("Hikari*.jar")
class DataSourceBuilderNoHikariTests {
@Test
void findTypeReturnsTomcatDataSource() {
assertThat(DataSourceBuilder.findType(null)).isEqualTo(org.apache.tomcat.jdbc.pool.DataSource.class);
}
@Test
void createAndBuildReturnsTomcatDataSource() {
DataSource dataSource = DataSourceBuilder.create().build();
assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceBuilderRuntimeHints}.
*
* @author Phillip Webb
*/
class DataSourceBuilderRuntimeHintsTests {
@Test
void shouldRegisterDataSourceConstructors() {
ReflectionHints hints = registerHints();
Stream
.of(com.mchange.v2.c3p0.ComboPooledDataSource.class, com.zaxxer.hikari.HikariDataSource.class,
oracle.jdbc.datasource.OracleDataSource.class, oracle.ucp.jdbc.PoolDataSource.class,
org.apache.commons.dbcp2.BasicDataSource.class, org.apache.tomcat.jdbc.pool.DataSource.class,
org.h2.jdbcx.JdbcDataSource.class, org.postgresql.ds.PGSimpleDataSource.class,
org.springframework.jdbc.datasource.SimpleDriverDataSource.class,
org.vibur.dbcp.ViburDBCPDataSource.class)
.forEach((dataSourceType) -> {
TypeHint typeHint = hints.getTypeHint(dataSourceType);
assertThat(typeHint).withFailMessage(() -> "No hints found for data source type " + dataSourceType)
.isNotNull();
Set<MemberCategory> memberCategories = typeHint.getMemberCategories();
assertThat(memberCategories).containsExactly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
});
}
private ReflectionHints registerHints() {
RuntimeHints hints = new RuntimeHints();
new DataSourceBuilderRuntimeHints().registerHints(hints, getClass().getClassLoader());
return hints.reflection();
}
}

View File

@@ -0,0 +1,718 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Arrays;
import java.util.logging.Logger;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.internal.OpaqueString;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.h2.Driver;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.postgresql.ds.PGSimpleDataSource;
import org.vibur.dbcp.ViburDBCPDataSource;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* Tests for {@link DataSourceBuilder}.
*
* @author Stephane Nicoll
* @author Fabio Grassi
* @author Phillip Webb
*/
class DataSourceBuilderTests {
private DataSource dataSource;
@AfterEach
void shutdownDataSource() throws IOException {
if (this.dataSource instanceof Closeable closeable) {
closeable.close();
}
}
@Test
void buildWhenHikariAvailableReturnsHikariDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").build();
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
assertThat(hikariDataSource.getJdbcUrl()).isEqualTo("jdbc:h2:test");
}
@Test // gh-26633
void buildWhenHikariDataSourceWithNullPasswordReturnsHikariDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").username("test").password(null).build();
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
assertThat(hikariDataSource.getJdbcUrl()).isEqualTo("jdbc:h2:test");
}
@Test
void buildWhenHikariNotAvailableReturnsTomcatDataSource() {
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari"))
.url("jdbc:h2:test")
.build();
assertThat(this.dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
}
@Test // gh-26633
void buildWhenTomcatDataSourceWithNullPasswordReturnsDataSource() {
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari"))
.url("jdbc:h2:test")
.username("test")
.password(null)
.build();
assertThat(this.dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
}
@Test
void buildWhenHikariAndTomcatNotAvailableReturnsDbcp2DataSource() {
this.dataSource = DataSourceBuilder
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool"))
.url("jdbc:h2:test")
.build();
assertThat(this.dataSource).isInstanceOf(BasicDataSource.class);
}
@Test // gh-26633
void buildWhenDbcp2DataSourceWithNullPasswordReturnsDbcp2DataSource() {
this.dataSource = DataSourceBuilder
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool"))
.url("jdbc:h2:test")
.username("test")
.password(null)
.build();
assertThat(this.dataSource).isInstanceOf(BasicDataSource.class);
}
@Test
void buildWhenHikariAndTomcatAndDbcpNotAvailableReturnsOracleUcpDataSource() {
this.dataSource = DataSourceBuilder
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool",
"org.apache.commons.dbcp2"))
.url("jdbc:h2:test")
.build();
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
}
@Test
void buildWhenHikariTypeSpecifiedReturnsExpectedDataSource() {
HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build();
assertThat(hikariDataSource).isInstanceOf(HikariDataSource.class);
}
@Test
void buildWhenSimpleDriverTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(SimpleDriverDataSource.class).build();
assertThat(this.dataSource).isInstanceOf(SimpleDriverDataSource.class);
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource;
assertThat(simpleDriverDataSource.getUrl()).isEqualTo("jdbc:h2:test");
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(Driver.class);
}
@Test
void buildWhenOracleTypeSpecifiedReturnsExpectedDataSource() throws SQLException {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:oracle:thin:@localhost:1521:xe")
.type(OracleDataSource.class)
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(OracleDataSource.class);
OracleDataSource oracleDataSource = (OracleDataSource) this.dataSource;
assertThat(oracleDataSource.getURL()).isEqualTo("jdbc:oracle:thin:@localhost:1521:xe");
assertThat(oracleDataSource.getUser()).isEqualTo("test");
}
@Test // gh-26631
void buildWhenOracleTypeSpecifiedWithDriverClassReturnsExpectedDataSource() throws SQLException {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:oracle:thin:@localhost:1521:xe")
.type(OracleDataSource.class)
.driverClassName("oracle.jdbc.pool.OracleDataSource")
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(OracleDataSource.class);
OracleDataSource oracleDataSource = (OracleDataSource) this.dataSource;
assertThat(oracleDataSource.getURL()).isEqualTo("jdbc:oracle:thin:@localhost:1521:xe");
assertThat(oracleDataSource.getUser()).isEqualTo("test");
}
@Test
void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.type(PoolDataSourceImpl.class)
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
PoolDataSourceImpl upcDataSource = (PoolDataSourceImpl) this.dataSource;
assertThat(upcDataSource.getConnectionFactoryClassName()).isEqualTo("org.hsqldb.jdbc.JDBCDriver");
assertThat(upcDataSource.getUser()).isEqualTo("test");
}
@Test
void buildWhenH2TypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:h2:test")
.type(JdbcDataSource.class)
.username("test")
.password("secret")
.build();
assertThat(this.dataSource).isInstanceOf(JdbcDataSource.class);
JdbcDataSource h2DataSource = (JdbcDataSource) this.dataSource;
assertThat(h2DataSource.getUser()).isEqualTo("test");
assertThat(h2DataSource.getPassword()).isEqualTo("secret");
}
@Test // gh-26631
void buildWhenH2TypeSpecifiedWithDriverClassReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:h2:test")
.type(JdbcDataSource.class)
.driverClassName("org.h2.jdbcx.JdbcDataSource")
.username("test")
.password("secret")
.build();
assertThat(this.dataSource).isInstanceOf(JdbcDataSource.class);
JdbcDataSource h2DataSource = (JdbcDataSource) this.dataSource;
assertThat(h2DataSource.getUser()).isEqualTo("test");
assertThat(h2DataSource.getPassword()).isEqualTo("secret");
}
@Test
void buildWhenPostgresTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:postgresql://localhost/test")
.type(PGSimpleDataSource.class)
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(PGSimpleDataSource.class);
PGSimpleDataSource pgDataSource = (PGSimpleDataSource) this.dataSource;
assertThat(pgDataSource.getUser()).isEqualTo("test");
}
@Test // gh-26631
void buildWhenPostgresTypeSpecifiedWithDriverClassReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:postgresql://localhost/test")
.type(PGSimpleDataSource.class)
.driverClassName("org.postgresql.ds.PGSimpleDataSource")
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(PGSimpleDataSource.class);
PGSimpleDataSource pgDataSource = (PGSimpleDataSource) this.dataSource;
assertThat(pgDataSource.getUser()).isEqualTo("test");
}
@Test // gh-26647
void buildWhenSqlServerTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:sqlserver://localhost/test")
.type(SQLServerDataSource.class)
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(SQLServerDataSource.class);
SQLServerDataSource sqlServerDataSource = (SQLServerDataSource) this.dataSource;
assertThat(sqlServerDataSource.getUser()).isEqualTo("test");
}
@Test
void buildWhenMappedTypeSpecifiedAndNoSuitableOptionalMappingBuilds() {
assertThatNoException().isThrownBy(
() -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build());
}
@Test
void buildWhenCustomSubclassTypeSpecifiedReturnsDataSourceWithOnlyBasePropertiesSet() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:h2:test")
.type(CustomTomcatDataSource.class)
.username("test")
.build();
assertThat(this.dataSource).isInstanceOf(CustomTomcatDataSource.class);
CustomTomcatDataSource testDataSource = (CustomTomcatDataSource) this.dataSource;
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
assertThat(testDataSource.getJdbcUrl()).isNull();
assertThat(testDataSource.getUsername()).isEqualTo("test");
assertThat(testDataSource.getUser()).isNull();
assertThat(testDataSource.getDriverClassName()).isEqualTo(Driver.class.getName());
assertThat(testDataSource.getDriverClass()).isNull();
}
@Test
void buildWhenCustomTypeSpecifiedReturnsDataSourceWithPropertiesSetViaReflection() {
this.dataSource = DataSourceBuilder.create()
.type(CustomDataSource.class)
.username("test")
.password("secret")
.url("jdbc:h2:test")
.driverClassName("com.example")
.build();
assertThat(this.dataSource).isInstanceOf(CustomDataSource.class);
CustomDataSource testDataSource = (CustomDataSource) this.dataSource;
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
assertThat(testDataSource.getUsername()).isEqualTo("test");
assertThat(testDataSource.getPassword()).isEqualTo("secret");
assertThat(testDataSource.getDriverClassName()).isEqualTo("com.example");
}
@Test
void buildWhenCustomTypeSpecifiedAndNoSuitableOptionalSetterBuilds() {
assertThatNoException().isThrownBy(() -> DataSourceBuilder.create()
.type(LimitedCustomDataSource.class)
.driverClassName("com.example")
.build());
}
@Test
void buildWhenCustomTypeSpecifiedAndNoSuitableMandatorySetterThrowsException() {
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(
() -> DataSourceBuilder.create().type(LimitedCustomDataSource.class).url("jdbc:com.example").build());
}
@Test
void buildWhenDerivedWithNewUrlReturnsNewDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setJdbcUrl("jdbc:h2:test");
HikariDataSource built = (HikariDataSource) DataSourceBuilder.derivedFrom(dataSource)
.url("jdbc:h2:test2")
.build();
assertThat(built.getUsername()).isEqualTo("test");
assertThat(built.getPassword()).isEqualTo("secret");
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test2");
}
@Test
void buildWhenDerivedWithNewUsernameAndPasswordReturnsNewDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setJdbcUrl("jdbc:h2:test");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource);
HikariDataSource built = (HikariDataSource) builder.username("test2").password("secret2").build();
assertThat(built.getUsername()).isEqualTo("test2");
assertThat(built.getPassword()).isEqualTo("secret2");
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
}
@Test
void buildWhenDerivedFromOracleDataSourceWithPasswordNotSetThrowsException() throws Exception {
oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
dataSource.setUser("test");
dataSource.setPassword("secret");
dataSource.setURL("example.com");
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class)
.isThrownBy(() -> DataSourceBuilder.derivedFrom(dataSource).url("example.org").build());
}
@Test
void buildWhenDerivedFromOracleUcpWithPasswordNotSetThrowsException() throws Exception {
PoolDataSource dataSource = new PoolDataSourceImpl();
dataSource.setUser("test");
dataSource.setPassword("secret");
dataSource.setURL("example.com");
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class)
.isThrownBy(() -> DataSourceBuilder.derivedFrom(dataSource).url("example.org").build());
}
@Test
void buildWhenDerivedFromOracleDataSourceWithPasswordSetReturnsDataSource() throws Exception {
oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
dataSource.setUser("test");
dataSource.setPassword("secret");
dataSource.setURL("example.com");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource);
oracle.jdbc.datasource.impl.OracleDataSource built = (oracle.jdbc.datasource.impl.OracleDataSource) builder
.username("test2")
.password("secret2")
.build();
assertThat(built.getUser()).isEqualTo("test2");
assertThat(built).extracting("password")
.extracting((opaque) -> ((OpaqueString) opaque).get())
.isEqualTo("secret2");
assertThat(built.getURL()).isEqualTo("example.com");
}
@Test
void buildWhenDerivedFromOracleUcpWithPasswordSetReturnsDataSource() throws SQLException {
PoolDataSource dataSource = new PoolDataSourceImpl();
dataSource.setUser("test");
dataSource.setPassword("secret");
dataSource.setURL("example.com");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource);
PoolDataSource built = (PoolDataSource) builder.username("test2").password("secret2").build();
assertThat(built.getUser()).isEqualTo("test2");
assertThat(built).extracting("password")
.extracting((opaque) -> ((oracle.ucp.util.OpaqueString) opaque).get())
.isEqualTo("secret2");
assertThat(built.getURL()).isEqualTo("example.com");
}
@Test
void buildWhenDerivedFromEmbeddedDatabase() {
EmbeddedDatabase database = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build();
SimpleDriverDataSource built = (SimpleDriverDataSource) DataSourceBuilder.derivedFrom(database)
.username("test")
.password("secret")
.build();
assertThat(built.getUsername()).isEqualTo("test");
assertThat(built.getPassword()).isEqualTo("secret");
assertThat(built.getUrl()).startsWith("jdbc:hsqldb:mem");
}
@Test
void buildWhenDerivedFromWrappedDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setJdbcUrl("jdbc:h2:test");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(wrap(wrap(dataSource)));
HikariDataSource built = (HikariDataSource) builder.username("test2").password("secret2").build();
assertThat(built.getUsername()).isEqualTo("test2");
assertThat(built.getPassword()).isEqualTo("secret2");
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
}
@Test // gh-26644
void buildWhenDerivedFromExistingDatabaseWithTypeChange() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource).type(SimpleDriverDataSource.class);
SimpleDriverDataSource built = (SimpleDriverDataSource) builder.username("test2").password("secret2").build();
assertThat(built.getUsername()).isEqualTo("test2");
assertThat(built.getPassword()).isEqualTo("secret2");
assertThat(built.getUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
}
@Test // gh-27295
void buildWhenDerivedFromCustomType() {
CustomDataSource dataSource = new CustomDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.username("alice")
.password("confidential");
CustomDataSource testSource = (CustomDataSource) builder.build();
assertThat(testSource).isNotSameAs(dataSource);
assertThat(testSource.getUsername()).isEqualTo("alice");
assertThat(testSource.getUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
assertThat(testSource.getPassword()).isEqualTo("confidential");
}
@Test // gh-27295
void buildWhenDerivedFromCustomTypeWithTypeChange() {
CustomDataSource dataSource = new CustomDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource).type(SimpleDriverDataSource.class);
SimpleDriverDataSource testSource = (SimpleDriverDataSource) builder.build();
assertThat(testSource.getUsername()).isEqualTo("test");
assertThat(testSource.getUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
assertThat(testSource.getPassword()).isEqualTo("secret");
}
@Test
void buildWhenDerivedFromCustomTypeDeriveDriverClassNameFromUrl() {
NoDriverClassNameDataSource dataSource = new NoDriverClassNameDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource).type(SimpleDriverDataSource.class);
SimpleDriverDataSource testSource = (SimpleDriverDataSource) builder.build();
assertThat(testSource.getUsername()).isEqualTo("test");
assertThat(testSource.getUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
assertThat(testSource.getPassword()).isEqualTo("secret");
assertThat(testSource.getDriver()).isInstanceOf(org.postgresql.Driver.class);
}
@Test
void buildWhenDerivedFromCustomTypeDeriveDriverClassNameFromOverridenUrl() {
NoDriverClassNameDataSource dataSource = new NoDriverClassNameDataSource();
dataSource.setUsername("test");
dataSource.setPassword("secret");
dataSource.setUrl("jdbc:mysql://localhost:5432/mysql");
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.type(SimpleDriverDataSource.class)
.url("jdbc:mariadb://localhost:5432/mariadb");
SimpleDriverDataSource testSource = (SimpleDriverDataSource) builder.build();
assertThat(testSource.getUsername()).isEqualTo("test");
assertThat(testSource.getUrl()).isEqualTo("jdbc:mariadb://localhost:5432/mariadb");
assertThat(testSource.getPassword()).isEqualTo("secret");
assertThat(testSource.getDriver()).isInstanceOf(org.mariadb.jdbc.Driver.class);
}
@Test // gh-31920
void buildWhenC3P0TypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:postgresql://localhost:5432/postgres")
.type(ComboPooledDataSource.class)
.username("test")
.password("secret")
.driverClassName("com.example.Driver")
.build();
assertThat(this.dataSource).isInstanceOf(ComboPooledDataSource.class);
ComboPooledDataSource c3p0DataSource = (ComboPooledDataSource) this.dataSource;
assertThat(c3p0DataSource.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
assertThat(c3p0DataSource.getUser()).isEqualTo("test");
assertThat(c3p0DataSource.getPassword()).isEqualTo("secret");
assertThat(c3p0DataSource.getDriverClass()).isEqualTo("com.example.Driver");
}
@Test // gh-42903
void buildWhenViburTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:postgresql://localhost:5432/postgres")
.type(ViburDBCPDataSource.class)
.username("test")
.password("secret")
.driverClassName("com.example.Driver")
.build();
assertThat(this.dataSource).isInstanceOf(ViburDBCPDataSource.class);
ViburDBCPDataSource viburDataSource = (ViburDBCPDataSource) this.dataSource;
assertThat(viburDataSource.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/postgres");
assertThat(viburDataSource.getUsername()).isEqualTo("test");
assertThat(viburDataSource.getPassword()).isEqualTo("secret");
assertThat(viburDataSource.getDriverClassName()).isEqualTo("com.example.Driver");
}
@Test
void buildWhenJdbcUrlIsFromUnknownDriverLeavesDriverClassNameUnset() {
this.dataSource = DataSourceBuilder.create()
.url("jdbc:example://localhost:1234/example")
.type(HikariDataSource.class)
.build();
assertThat(((HikariDataSource) this.dataSource).getDriverClassName()).isNull();
}
private DataSource wrap(DataSource target) {
return new DataSourceWrapper(target);
}
private static final class DataSourceWrapper implements DataSource {
private final DataSource delegate;
private DataSourceWrapper(DataSource delegate) {
this.delegate = delegate;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return this.delegate.getParentLogger();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return this.delegate.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return this.delegate.isWrapperFor(iface);
}
@Override
public Connection getConnection() throws SQLException {
return this.delegate.getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return this.delegate.getConnection(username, password);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return this.delegate.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
this.delegate.setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
this.delegate.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() throws SQLException {
return this.delegate.getLoginTimeout();
}
}
final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
HidePackagesClassLoader(String... hiddenPackages) {
super(new URL[0], HidePackagesClassLoader.class.getClassLoader());
this.hiddenPackages = hiddenPackages;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (Arrays.stream(this.hiddenPackages).anyMatch(name::startsWith)) {
throw new ClassNotFoundException();
}
return super.loadClass(name, resolve);
}
}
static class CustomTomcatDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
private String jdbcUrl;
private String user;
private String driverClass;
String getJdbcUrl() {
return this.jdbcUrl;
}
void setJdbcUrl(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
String getUser() {
return this.user;
}
void setUser(String user) {
this.user = user;
}
String getDriverClass() {
return this.driverClass;
}
void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
}
static class LimitedCustomDataSource extends AbstractDataSource {
private String username;
private String password;
@Override
public Connection getConnection() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
throw new UnsupportedOperationException();
}
String getUsername() {
return this.username;
}
void setUsername(String username) {
this.username = username;
}
String getPassword() {
return this.password;
}
void setPassword(String password) {
this.password = password;
}
}
static class NoDriverClassNameDataSource extends LimitedCustomDataSource {
private String url;
String getUrl() {
return this.url;
}
void setUrl(String url) {
this.url = url;
}
}
static class CustomDataSource extends LimitedCustomDataSource {
private String driverClassName;
private String url;
String getDriverClassName() {
return this.driverClassName;
}
void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
String getUrl() {
return this.url;
}
void setUrl(String url) {
this.url = url;
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link DataSourceUnwrapper} when spring-jdbc is not available.
*
* @author Stephane Nicoll
*/
@ClassPathExclusions("spring-jdbc-*.jar")
class DataSourceUnwrapperNoSpringJdbcTests {
@Test
void unwrapWithProxy() {
DataSource dataSource = new HikariDataSource();
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapDataSourceProxy() {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, PoolConfiguration.class, DataSourceProxy.class))
.isSameAs(dataSource);
}
private DataSource wrapInProxy(DataSource dataSource) {
return (DataSource) new ProxyFactory(dataSource).getProxy();
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.sql.SQLException;
import java.util.function.Consumer;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.jdbc.datasource.SmartDataSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DataSourceUnwrapper}.
*
* @author Stephane Nicoll
*/
class DataSourceUnwrapperTests {
@Test
void unwrapWithTarget() {
DataSource dataSource = new HikariDataSource();
assertThat(DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapWithWrongTarget() {
DataSource dataSource = new HikariDataSource();
assertThat(DataSourceUnwrapper.unwrap(dataSource, SmartDataSource.class, SingleConnectionDataSource.class))
.isNull();
}
@Test
void unwrapWithDelegate() {
DataSource dataSource = new HikariDataSource();
DataSource actual = wrapInDelegate(wrapInDelegate(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapWithProxy() {
DataSource dataSource = new HikariDataSource();
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapWithProxyAndDelegate() {
DataSource dataSource = new HikariDataSource();
DataSource actual = wrapInProxy(wrapInDelegate(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapWithSeveralLevelOfWrapping() {
DataSource dataSource = new HikariDataSource();
DataSource actual = wrapInProxy(wrapInDelegate(wrapInDelegate(wrapInProxy(wrapInDelegate(dataSource)))));
assertThat(DataSourceUnwrapper.unwrap(actual, HikariConfigMXBean.class, HikariDataSource.class))
.isSameAs(dataSource);
}
@Test
void unwrapDataSourceProxy() {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
DataSource actual = wrapInDelegate(wrapInProxy(dataSource));
assertThat(DataSourceUnwrapper.unwrap(actual, PoolConfiguration.class, DataSourceProxy.class))
.isSameAs(dataSource);
}
@Test
void unwrappingIsNotAttemptedWhenTargetIsNotAnInterface() {
DataSource dataSource = mock(DataSource.class);
assertThat(DataSourceUnwrapper.unwrap(dataSource, HikariDataSource.class)).isNull();
then(dataSource).shouldHaveNoMoreInteractions();
}
@Test
void unwrappingIsNotAttemptedWhenDataSourceIsNotWrapperForTarget() throws SQLException {
DataSource dataSource = mock(DataSource.class);
assertThat(DataSourceUnwrapper.unwrap(dataSource, Consumer.class)).isNull();
then(dataSource).should().isWrapperFor(Consumer.class);
then(dataSource).shouldHaveNoMoreInteractions();
}
private DataSource wrapInProxy(DataSource dataSource) {
return (DataSource) new ProxyFactory(dataSource).getProxy();
}
private DataSource wrapInDelegate(DataSource dataSource) {
return new DelegatingDataSource(dataSource);
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.io.IOException;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.sql.XADataSource;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.asm.ClassReader;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for the class names in the {@link DatabaseDriver} enumeration.
*
* @author Andy Wilkinson
*/
class DatabaseDriverClassNameTests {
private static final Set<DatabaseDriver> EXCLUDED_DRIVERS = Collections
.unmodifiableSet(EnumSet.of(DatabaseDriver.UNKNOWN, DatabaseDriver.DB2_AS400, DatabaseDriver.INFORMIX,
DatabaseDriver.HANA, DatabaseDriver.PHOENIX, DatabaseDriver.TERADATA, DatabaseDriver.REDSHIFT));
@ParameterizedTest(name = "{0} {2}")
@MethodSource
void databaseClassIsOfRequiredType(DatabaseDriver driver, String className, Class<?> requiredType)
throws Exception {
assertThat(getInterfaceNames(className.replace('.', '/'))).contains(requiredType.getName().replace('.', '/'));
}
private List<String> getInterfaceNames(String className) throws IOException {
// Use ASM to avoid unwanted side effects of loading JDBC drivers
ClassReader classReader = new ClassReader(getClass().getResourceAsStream("/" + className + ".class"));
List<String> interfaceNames = new ArrayList<>();
for (String name : classReader.getInterfaces()) {
interfaceNames.add(name);
interfaceNames.addAll(getInterfaceNames(name));
}
String superName = classReader.getSuperName();
if (superName != null) {
interfaceNames.addAll(getInterfaceNames(superName));
}
return interfaceNames;
}
static Stream<? extends Arguments> databaseClassIsOfRequiredType() {
return Stream.concat(argumentsForType(Driver.class, DatabaseDriver::getDriverClassName),
argumentsForType(XADataSource.class,
(databaseDriver) -> databaseDriver.getXaDataSourceClassName() != null,
DatabaseDriver::getXaDataSourceClassName));
}
private static Stream<? extends Arguments> argumentsForType(Class<?> type,
Function<DatabaseDriver, String> classNameExtractor) {
return argumentsForType(type, (databaseDriver) -> true, classNameExtractor);
}
private static Stream<? extends Arguments> argumentsForType(Class<?> type, Predicate<DatabaseDriver> predicate,
Function<DatabaseDriver, String> classNameExtractor) {
return Stream.of(DatabaseDriver.values())
.filter((databaseDriver) -> !EXCLUDED_DRIVERS.contains(databaseDriver))
.filter(predicate)
.map((databaseDriver) -> Arguments.of(databaseDriver, classNameExtractor.apply(databaseDriver), type));
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link DatabaseDriver}.
*
* @author Phillip Webb
* @author Maciej Walkowiak
* @author Stephane Nicoll
*/
class DatabaseDriverTests {
@Test
void classNameForKnownDatabase() {
String driverClassName = DatabaseDriver.fromJdbcUrl("jdbc:postgresql://hostname/dbname").getDriverClassName();
assertThat(driverClassName).isEqualTo("org.postgresql.Driver");
}
@Test
void nullClassNameForUnknownDatabase() {
String driverClassName = DatabaseDriver.fromJdbcUrl("jdbc:unknowndb://hostname/dbname").getDriverClassName();
assertThat(driverClassName).isNull();
}
@Test
void unknownOnNullJdbcUrl() {
DatabaseDriver actual = DatabaseDriver.fromJdbcUrl(null);
assertThat(actual).isEqualTo(DatabaseDriver.UNKNOWN);
}
@Test
void failureOnMalformedJdbcUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> DatabaseDriver.fromJdbcUrl("malformed:url"))
.withMessageContaining("'url' must start with");
}
@Test
void unknownOnNullProductName() {
DatabaseDriver actual = DatabaseDriver.fromProductName(null);
assertThat(actual).isEqualTo(DatabaseDriver.UNKNOWN);
}
@Test
void databaseProductNameLookups() {
assertThat(DatabaseDriver.fromProductName("newone")).isEqualTo(DatabaseDriver.UNKNOWN);
assertThat(DatabaseDriver.fromProductName("Apache Derby")).isEqualTo(DatabaseDriver.DERBY);
assertThat(DatabaseDriver.fromProductName("H2")).isEqualTo(DatabaseDriver.H2);
assertThat(DatabaseDriver.fromProductName("HDB")).isEqualTo(DatabaseDriver.HANA);
assertThat(DatabaseDriver.fromProductName("HSQL Database Engine")).isEqualTo(DatabaseDriver.HSQLDB);
assertThat(DatabaseDriver.fromProductName("SQLite")).isEqualTo(DatabaseDriver.SQLITE);
assertThat(DatabaseDriver.fromProductName("MySQL")).isEqualTo(DatabaseDriver.MYSQL);
assertThat(DatabaseDriver.fromProductName("MariaDB")).isEqualTo(DatabaseDriver.MARIADB);
assertThat(DatabaseDriver.fromProductName("Oracle")).isEqualTo(DatabaseDriver.ORACLE);
assertThat(DatabaseDriver.fromProductName("PostgreSQL")).isEqualTo(DatabaseDriver.POSTGRESQL);
assertThat(DatabaseDriver.fromProductName("Redshift")).isEqualTo(DatabaseDriver.REDSHIFT);
assertThat(DatabaseDriver.fromProductName("Microsoft SQL Server")).isEqualTo(DatabaseDriver.SQLSERVER);
assertThat(DatabaseDriver.fromProductName("SQL SERVER")).isEqualTo(DatabaseDriver.SQLSERVER);
assertThat(DatabaseDriver.fromProductName("DB2")).isEqualTo(DatabaseDriver.DB2);
assertThat(DatabaseDriver.fromProductName("Firebird 2.5.WI")).isEqualTo(DatabaseDriver.FIREBIRD);
assertThat(DatabaseDriver.fromProductName("Firebird 2.1.LI")).isEqualTo(DatabaseDriver.FIREBIRD);
assertThat(DatabaseDriver.fromProductName("DB2/LINUXX8664")).isEqualTo(DatabaseDriver.DB2);
assertThat(DatabaseDriver.fromProductName("DB2 UDB for AS/400")).isEqualTo(DatabaseDriver.DB2_AS400);
assertThat(DatabaseDriver.fromProductName("DB3 XDB for AS/400")).isEqualTo(DatabaseDriver.DB2_AS400);
assertThat(DatabaseDriver.fromProductName("Teradata")).isEqualTo(DatabaseDriver.TERADATA);
assertThat(DatabaseDriver.fromProductName("Informix Dynamic Server")).isEqualTo(DatabaseDriver.INFORMIX);
assertThat(DatabaseDriver.fromProductName("Apache Phoenix")).isEqualTo(DatabaseDriver.PHOENIX);
assertThat(DatabaseDriver.fromProductName("ClickHouse")).isEqualTo(DatabaseDriver.CLICKHOUSE);
}
@Test
void databaseJdbcUrlLookups() {
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:newone://localhost")).isEqualTo(DatabaseDriver.UNKNOWN);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:derby:sample")).isEqualTo(DatabaseDriver.DERBY);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:h2:~/sample")).isEqualTo(DatabaseDriver.H2);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:hsqldb:hsql://localhost")).isEqualTo(DatabaseDriver.HSQLDB);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:sqlite:sample.db")).isEqualTo(DatabaseDriver.SQLITE);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:mysql://localhost:3306/sample")).isEqualTo(DatabaseDriver.MYSQL);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:oracle:thin:@localhost:1521:orcl"))
.isEqualTo(DatabaseDriver.ORACLE);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:postgresql://127.0.0.1:5432/sample"))
.isEqualTo(DatabaseDriver.POSTGRESQL);
assertThat(DatabaseDriver
.fromJdbcUrl("jdbc:redshift://examplecluster.abc123xyz789.us-west-2.redshift.amazonaws.com:5439/sample"))
.isEqualTo(DatabaseDriver.REDSHIFT);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:jtds:sqlserver://127.0.0.1:1433/sample"))
.isEqualTo(DatabaseDriver.JTDS);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:sap:localhost")).isEqualTo(DatabaseDriver.HANA);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:sqlserver://127.0.0.1:1433")).isEqualTo(DatabaseDriver.SQLSERVER);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:firebirdsql://localhost/sample"))
.isEqualTo(DatabaseDriver.FIREBIRD);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:firebird://localhost/sample")).isEqualTo(DatabaseDriver.FIREBIRD);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:db2://localhost:50000/sample ")).isEqualTo(DatabaseDriver.DB2);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:as400://localhost")).isEqualTo(DatabaseDriver.DB2_AS400);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:teradata://localhost/SAMPLE")).isEqualTo(DatabaseDriver.TERADATA);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:informix-sqli://localhost:1533/sample"))
.isEqualTo(DatabaseDriver.INFORMIX);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:informix-direct://sample")).isEqualTo(DatabaseDriver.INFORMIX);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:phoenix:localhost")).isEqualTo(DatabaseDriver.PHOENIX);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:tc:mysql://localhost:3306/sample"))
.isEqualTo(DatabaseDriver.TESTCONTAINERS);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:clickhouse://localhost:3306/sample"))
.isEqualTo(DatabaseDriver.CLICKHOUSE);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:ch://localhost:3306/sample")).isEqualTo(DatabaseDriver.CLICKHOUSE);
assertThat(DatabaseDriver.fromJdbcUrl("jdbc:aws-wrapper:postgresql://127.0.0.1:5432/sample"))
.isEqualTo(DatabaseDriver.AWS_WRAPPER);
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EmbeddedDatabaseConnection}.
*
* @author Stephane Nicoll
* @author Nidhi Desai
*/
class EmbeddedDatabaseConnectionTests {
@Test
void h2CustomDatabaseName() {
assertThat(EmbeddedDatabaseConnection.H2.getUrl("mydb"))
.isEqualTo("jdbc:h2:mem:mydb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
}
@Test
void derbyCustomDatabaseName() {
assertThat(EmbeddedDatabaseConnection.DERBY.getUrl("myderbydb"))
.isEqualTo("jdbc:derby:memory:myderbydb;create=true");
}
@Test
void hsqldbCustomDatabaseName() {
assertThat(EmbeddedDatabaseConnection.HSQLDB.getUrl("myhsqldb")).isEqualTo("jdbc:hsqldb:mem:myhsqldb");
}
@Test
void getUrlWithNullDatabaseNameForHsqldb() {
assertThatIllegalArgumentException().isThrownBy(() -> EmbeddedDatabaseConnection.HSQLDB.getUrl(null))
.withMessageContaining("'databaseName' must not be empty");
}
@Test
void getUrlWithEmptyDatabaseNameForHsqldb() {
assertThatIllegalArgumentException().isThrownBy(() -> EmbeddedDatabaseConnection.HSQLDB.getUrl(" "))
.withMessageContaining("'databaseName' must not be empty");
}
@ParameterizedTest(name = "{0} - {1}")
@MethodSource("embeddedDriverAndUrlParameters")
void isEmbeddedWithDriverAndUrl(String driverClassName, String url, boolean embedded) {
assertThat(EmbeddedDatabaseConnection.isEmbedded(driverClassName, url)).isEqualTo(embedded);
}
static Object[] embeddedDriverAndUrlParameters() {
return new Object[] {
new Object[] { EmbeddedDatabaseConnection.H2.getDriverClassName(), "jdbc:h2:~/test", false },
new Object[] { EmbeddedDatabaseConnection.H2.getDriverClassName(), "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
true },
new Object[] { EmbeddedDatabaseConnection.H2.getDriverClassName(), null, true },
new Object[] { EmbeddedDatabaseConnection.HSQLDB.getDriverClassName(), "jdbc:hsqldb:hsql://localhost",
false },
new Object[] { EmbeddedDatabaseConnection.HSQLDB.getDriverClassName(), "jdbc:hsqldb:mem:test", true },
new Object[] { EmbeddedDatabaseConnection.HSQLDB.getDriverClassName(), null, true },
new Object[] { EmbeddedDatabaseConnection.DERBY.getDriverClassName(), "jdbc:derby:memory:test", true },
new Object[] { EmbeddedDatabaseConnection.DERBY.getDriverClassName(), null, true },
new Object[] { "com.mysql.cj.jdbc.Driver", "jdbc:mysql:mem:test", false },
new Object[] { "com.mysql.cj.jdbc.Driver", null, false },
new Object[] { null, "jdbc:none:mem:test", false }, new Object[] { null, null, false } };
}
@Test
void isEmbeddedWithH2DataSource() {
testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build());
}
@Test
void isEmbeddedWithHsqlDataSource() {
testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build());
}
@Test
void isEmbeddedWithDerbyDataSource() {
testEmbeddedDatabase(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY).build());
}
void testEmbeddedDatabase(EmbeddedDatabase database) {
try {
assertThat(EmbeddedDatabaseConnection.isEmbedded(database)).isTrue();
}
finally {
database.shutdown();
}
}
@Test
void isEmbeddedWithUnknownDataSource() throws SQLException {
assertThat(EmbeddedDatabaseConnection.isEmbedded(mockDataSource("unknown-db", null))).isFalse();
}
@Test
void isEmbeddedWithH2File() throws SQLException {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(mockDataSource(EmbeddedDatabaseConnection.H2.getDriverClassName(), "jdbc:h2:~/test")))
.isFalse();
}
@Test
void isEmbeddedWithMissingDriverClassMetadata() throws SQLException {
assertThat(EmbeddedDatabaseConnection.isEmbedded(mockDataSource(null, "jdbc:h2:meme:test"))).isFalse();
}
@Test
void isEmbeddedWithMissingUrlMetadata() throws SQLException {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(mockDataSource(EmbeddedDatabaseConnection.H2.getDriverClassName(), null))).isTrue();
}
DataSource mockDataSource(String productName, String connectionUrl) throws SQLException {
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
given(metaData.getDatabaseProductName()).willReturn(productName);
given(metaData.getURL()).willReturn(connectionUrl);
Connection connection = mock(Connection.class);
given(connection.getMetaData()).willReturn(metaData);
DataSource dataSource = mock(DataSource.class);
given(dataSource.getConnection()).willReturn(connection);
return dataSource;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc;
import java.util.UUID;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HikariCheckpointRestoreLifecycle}.
*
* @author Christoph Strobl
* @author Andy Wilkinson
*/
class HikariCheckpointRestoreLifecycleTests {
private final HikariCheckpointRestoreLifecycle lifecycle;
private final HikariDataSource dataSource;
HikariCheckpointRestoreLifecycleTests() {
HikariConfig config = new HikariConfig();
config.setAllowPoolSuspension(true);
config.setJdbcUrl("jdbc:hsqldb:mem:test-" + UUID.randomUUID());
config.setPoolName("lifecycle-tests");
this.dataSource = new HikariDataSource(config);
this.lifecycle = new HikariCheckpointRestoreLifecycle(this.dataSource,
mock(ConfigurableApplicationContext.class));
}
@Test
void startedWhenStartedShouldSucceed() {
assertThat(this.lifecycle.isRunning()).isTrue();
this.lifecycle.start();
assertThat(this.lifecycle.isRunning()).isTrue();
}
@Test
void stopWhenStoppedShouldSucceed() {
assertThat(this.lifecycle.isRunning()).isTrue();
this.lifecycle.stop();
assertThat(this.dataSource.isRunning()).isFalse();
assertThatNoException().isThrownBy(this.lifecycle::stop);
}
@Test
void whenStoppedAndStartedDataSourceShouldPauseAndResume() {
assertThat(this.lifecycle.isRunning()).isTrue();
this.lifecycle.stop();
assertThat(this.dataSource.isRunning()).isFalse();
assertThat(this.dataSource.isClosed()).isFalse();
assertThat(this.lifecycle.isRunning()).isFalse();
assertThat(this.dataSource.getHikariPoolMXBean().getTotalConnections()).isZero();
this.lifecycle.start();
assertThat(this.dataSource.isRunning()).isTrue();
assertThat(this.dataSource.isClosed()).isFalse();
assertThat(this.lifecycle.isRunning()).isTrue();
}
@Test
void whenDataSourceIsClosedThenStartShouldThrow() {
this.dataSource.close();
assertThatExceptionOfType(RuntimeException.class).isThrownBy(this.lifecycle::start);
}
@Test
void startHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
mock(DataSource.class), mock(ConfigurableApplicationContext.class));
assertThat(nonHikariLifecycle.isRunning()).isFalse();
nonHikariLifecycle.start();
assertThat(nonHikariLifecycle.isRunning()).isFalse();
}
@Test
void stopHasNoEffectWhenDataSourceIsNotAHikariDataSource() {
HikariCheckpointRestoreLifecycle nonHikariLifecycle = new HikariCheckpointRestoreLifecycle(
mock(DataSource.class), mock(ConfigurableApplicationContext.class));
assertThat(nonHikariLifecycle.isRunning()).isFalse();
nonHikariLifecycle.stop();
assertThat(nonHikariLifecycle.isRunning()).isFalse();
}
}

View File

@@ -0,0 +1,400 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import io.r2dbc.spi.ConnectionFactory;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DataSourceAutoConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DataSourceAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt());
@Test
void testDefaultDataSourceExists() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DataSource.class));
}
@Test
void testDataSourceHasEmbeddedDefault() {
this.contextRunner.run((context) -> {
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
assertThat(dataSource.getJdbcUrl()).isNotNull();
assertThat(dataSource.getDriverClassName()).isNotNull();
});
}
@Test
void testBadUrl() {
this.contextRunner.withPropertyValues("spring.datasource.url:jdbc:not-going-to-work")
.withClassLoader(new DisableEmbeddedDatabaseClassLoader())
.run((context) -> assertThat(context).getFailure().isInstanceOf(BeanCreationException.class));
}
@Test
void testBadDriverClass() {
this.contextRunner.withPropertyValues("spring.datasource.driverClassName:org.none.jdbcDriver")
.run((context) -> assertThat(context).getFailure()
.isInstanceOf(BeanCreationException.class)
.hasMessageContaining("org.none.jdbcDriver"));
}
@Test
void datasourceWhenConnectionFactoryPresentIsNotAutoConfigured() {
this.contextRunner.withBean(ConnectionFactory.class, () -> mock(ConnectionFactory.class))
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
}
@Test
void hikariValidatesConnectionByDefault() {
assertDataSource(HikariDataSource.class, Collections.singletonList("org.apache.tomcat"), (dataSource) ->
// Use Connection#isValid()
assertThat(dataSource.getConnectionTestQuery()).isNull());
}
@Test
void tomcatIsFallback() {
assertDataSource(org.apache.tomcat.jdbc.pool.DataSource.class, Collections.singletonList("com.zaxxer.hikari"),
(dataSource) -> assertThat(dataSource.getUrl()).startsWith("jdbc:hsqldb:mem:testdb"));
}
@Test
void tomcatValidatesConnectionByDefault() {
assertDataSource(org.apache.tomcat.jdbc.pool.DataSource.class, Collections.singletonList("com.zaxxer.hikari"),
(dataSource) -> {
assertThat(dataSource.isTestOnBorrow()).isTrue();
assertThat(dataSource.getValidationQuery()).isEqualTo(DatabaseDriver.HSQLDB.getValidationQuery());
});
}
@Test
void commonsDbcp2IsFallback() {
assertDataSource(BasicDataSource.class, Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"),
(dataSource) -> assertThat(dataSource.getUrl()).startsWith("jdbc:hsqldb:mem:testdb"));
}
@Test
void commonsDbcp2ValidatesConnectionByDefault() {
assertDataSource(org.apache.commons.dbcp2.BasicDataSource.class,
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> {
assertThat(dataSource.getTestOnBorrow()).isTrue();
// Use Connection#isValid()
assertThat(dataSource.getValidationQuery()).isNull();
});
}
@Test
void oracleUcpIsFallback() {
assertDataSource(PoolDataSourceImpl.class,
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"),
(dataSource) -> assertThat(dataSource.getURL()).startsWith("jdbc:hsqldb:mem:testdb"));
}
@Test
void oracleUcpDoesNotValidateConnectionByDefault() {
assertDataSource(PoolDataSourceImpl.class,
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"), (dataSource) -> {
assertThat(dataSource.getValidateConnectionOnBorrow()).isFalse();
// Use an internal ping when using an Oracle JDBC driver
assertThat(dataSource.getSQLForValidateConnection()).isNull();
});
}
@Test
@SuppressWarnings("resource")
void testEmbeddedTypeDefaultsUsername() {
this.contextRunner
.withPropertyValues("spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb")
.run((context) -> {
DataSource bean = context.getBean(DataSource.class);
HikariDataSource pool = (HikariDataSource) bean;
assertThat(pool.getDriverClassName()).isEqualTo("org.hsqldb.jdbcDriver");
assertThat(pool.getUsername()).isEqualTo("sa");
});
}
@Test
void dataSourceWhenNoConnectionPoolsAreAvailableWithUrlDoesNotCreateDataSource() {
this.contextRunner.with(hideConnectionPools())
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testdb")
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
}
/**
* This test makes sure that if no supported data source is present, a datasource is
* still created if "spring.datasource.type" is present.
*/
@Test
void dataSourceWhenNoConnectionPoolsAreAvailableWithUrlAndTypeCreatesDataSource() {
this.contextRunner.with(hideConnectionPools())
.withPropertyValues("spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb",
"spring.datasource.type:" + SimpleDriverDataSource.class.getName())
.run(this::containsOnlySimpleDriverDataSource);
}
@Test
void explicitTypeSupportedDataSource() {
this.contextRunner
.withPropertyValues("spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb",
"spring.datasource.type:" + SimpleDriverDataSource.class.getName())
.run(this::containsOnlySimpleDriverDataSource);
}
private void containsOnlySimpleDriverDataSource(AssertableApplicationContext context) {
assertThat(context).hasSingleBean(DataSource.class);
assertThat(context).getBean(DataSource.class).isExactlyInstanceOf(SimpleDriverDataSource.class);
}
@Test
void testExplicitDriverClassClearsUsername() {
this.contextRunner
.withPropertyValues("spring.datasource.driverClassName:" + DatabaseTestDriver.class.getName(),
"spring.datasource.url:jdbc:foo://localhost")
.run((context) -> {
assertThat(context).hasSingleBean(DataSource.class);
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseTestDriver.class.getName());
assertThat(dataSource.getUsername()).isNull();
});
}
@Test
void testDefaultDataSourceCanBeOverridden() {
this.contextRunner.withUserConfiguration(TestDataSourceConfiguration.class)
.run((context) -> assertThat(context).getBean(DataSource.class).isInstanceOf(BasicDataSource.class));
}
@Test
void whenThereIsAUserProvidedDataSourceAnUnresolvablePlaceholderDoesNotCauseAProblem() {
this.contextRunner.withUserConfiguration(TestDataSourceConfiguration.class)
.withPropertyValues("spring.datasource.url:${UNRESOLVABLE_PLACEHOLDER}")
.run((context) -> assertThat(context).getBean(DataSource.class).isInstanceOf(BasicDataSource.class));
}
@Test
void whenThereIsAnEmptyUserProvidedDataSource() {
this.contextRunner.with(hideConnectionPools())
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.withPropertyValues("spring.datasource.url:")
.run((context) -> assertThat(context).getBean(DataSource.class).isInstanceOf(EmbeddedDatabase.class));
}
@Test
void whenNoInitializationRelatedSpringDataSourcePropertiesAreConfiguredThenInitializationBacksOff() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(DataSourceScriptDatabaseInitializer.class));
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesJdbcConnectionDetails.class));
}
@Test
void dbcp2UsesCustomConnectionDetailsWhenDefined() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource",
"spring.datasource.dbcp2.url=jdbc:broken", "spring.datasource.dbcp2.username=alice",
"spring.datasource.dbcp2.password=secret")
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new);
runner.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(BasicDataSource.class))
.satisfies((dbcp2) -> {
assertThat(dbcp2.getUserName()).isEqualTo("user-1");
assertThat(dbcp2).extracting("password").isEqualTo("password-1");
assertThat(dbcp2.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
assertThat(dbcp2.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
});
});
}
@Test
void genericUsesCustomJdbcConnectionDetailsWhenAvailable() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=" + TestDataSource.class.getName())
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new);
runner.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(TestDataSource.class);
TestDataSource source = (TestDataSource) dataSource;
assertThat(source.getUsername()).isEqualTo("user-1");
assertThat(source.getPassword()).isEqualTo("password-1");
assertThat(source.getDriver().getClass().getName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
assertThat(source.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
});
}
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "org.vibur.dbcp", "com.mchange"));
}
private <T extends DataSource> void assertDataSource(Class<T> expectedType, List<String> hiddenPackages,
Consumer<T> consumer) {
FilteredClassLoader classLoader = new FilteredClassLoader(StringUtils.toStringArray(hiddenPackages));
this.contextRunner.withClassLoader(classLoader).run((context) -> {
DataSource bean = context.getBean(DataSource.class);
assertThat(bean).isInstanceOf(expectedType);
consumer.accept(expectedType.cast(bean));
});
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlJdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class TestDataSourceConfiguration {
private BasicDataSource pool;
@Bean
DataSource dataSource() {
this.pool = new BasicDataSource();
this.pool.setDriverClassName("org.hsqldb.jdbcDriver");
this.pool.setUrl("jdbc:hsqldb:mem:overridedb");
this.pool.setUsername("sa");
return this.pool;
}
}
// see testExplicitDriverClassClearsUsername
public static class DatabaseTestDriver implements Driver {
@Override
public Connection connect(String url, Properties info) {
return mock(Connection.class);
}
@Override
public boolean acceptsURL(String url) {
return true;
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return 1;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public Logger getParentLogger() {
return mock(Logger.class);
}
}
static class DisableEmbeddedDatabaseClassLoader extends URLClassLoader {
DisableEmbeddedDatabaseClassLoader() {
super(new URL[0], DisableEmbeddedDatabaseClassLoader.class.getClassLoader());
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (name.equals(candidate.getDriverClassName())) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.util.Random;
import java.util.function.Function;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceAutoConfiguration} without spring-jdbc on the classpath.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions("spring-jdbc-*.jar")
class DataSourceAutoConfigurationWithoutSpringJdbcTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
@Test
void pooledDataSourceCanBeAutoConfigured() {
this.contextRunner.run((context) -> {
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
assertThat(dataSource.getJdbcUrl()).isNotNull();
assertThat(dataSource.getDriverClassName()).isNotNull();
});
}
@Test
void withoutConnectionPoolsAutoConfigurationBacksOff() {
this.contextRunner.with(hideConnectionPools())
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
}
@Test
void withUrlAndWithoutConnectionPoolsAutoConfigurationBacksOff() {
this.contextRunner.with(hideConnectionPools())
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt())
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
}
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange"));
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceBeanCreationFailureAnalyzer}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
@ClassPathExclusions({ "derby-*.jar", "derbytools-*.jar", "h2-*.jar", "hsqldb-*.jar" })
class DataSourceBeanCreationFailureAnalyzerTests {
private final MockEnvironment environment = new MockEnvironment();
@Test
void failureAnalysisIsPerformed() {
FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class);
assertThat(failureAnalysis.getDescription()).contains("'url' attribute is not specified",
"no embedded datasource could be configured", "Failed to determine a suitable driver class");
assertThat(failureAnalysis.getAction()).contains(
"If you want an embedded database (H2, HSQL or Derby), please put it on the classpath",
"If you have database settings to be loaded from a particular profile you may need to activate it",
"(no profiles are currently active)");
}
@Test
void failureAnalysisIsPerformedWithActiveProfiles() {
this.environment.setActiveProfiles("first", "second");
FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class);
assertThat(failureAnalysis.getAction()).contains("(the profiles first,second are currently active)");
}
private FailureAnalysis performAnalysis(Class<?> configuration) {
BeanCreationException failure = createFailure(configuration);
assertThat(failure).isNotNull();
DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer(
this.environment);
return failureAnalyzer.analyze(failure);
}
private BeanCreationException createFailure(Class<?> configuration) {
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setEnvironment(this.environment);
context.register(configuration);
context.refresh();
context.close();
return null;
}
catch (BeanCreationException ex) {
return ex;
}
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration(DataSourceAutoConfiguration.class)
static class TestConfiguration {
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.sql.init.ApplicationScriptDatabaseInitializer;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DataSourceInitializationAutoConfiguration}.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DataSourceInitializationAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceInitializationAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name:true");
@Test
void whenNoDataSourceIsAvailableThenAutoConfigurationBacksOff() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class));
}
@Test
void whenDataSourceIsAvailableThenDataSourceInitializerIsAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(DataSourceScriptDatabaseInitializer.class));
}
@Test
void whenDataSourceIsAvailableAndModeIsNeverThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.sql.init.mode:never")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class));
}
@Test
void whenAnApplicationInitializerIsDefinedThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withUserConfiguration(ApplicationDatabaseInitializerConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ApplicationScriptDatabaseInitializer.class)
.hasBean("customInitializer"));
}
@Test
void whenAnInitializerIsDefinedThenApplicationInitializerIsStillAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withUserConfiguration(DatabaseInitializerConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ApplicationDataSourceScriptDatabaseInitializer.class)
.hasBean("customInitializer"));
}
@Test
void whenBeanIsAnnotatedAsDependingOnDatabaseInitializationThenItDependsOnDataSourceScriptDatabaseInitializer() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withUserConfiguration(DependsOnInitializedDatabaseConfiguration.class)
.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(
"dataSourceInitializationAutoConfigurationTests.DependsOnInitializedDatabaseConfiguration");
assertThat(beanDefinition.getDependsOn())
.containsExactlyInAnyOrder("dataSourceScriptDatabaseInitializer");
});
}
@Test
void whenADataSourceIsAvailableAndSpringJdbcIsNotThenAutoConfigurationBacksOff() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(DatabasePopulator.class))
.run((context) -> {
assertThat(context).hasSingleBean(DataSource.class);
assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class);
});
}
@Configuration(proxyBeanMethods = false)
static class ApplicationDatabaseInitializerConfiguration {
@Bean
ApplicationScriptDatabaseInitializer customInitializer() {
return mock(ApplicationScriptDatabaseInitializer.class);
}
}
@Configuration(proxyBeanMethods = false)
static class DatabaseInitializerConfiguration {
@Bean
DataSourceScriptDatabaseInitializer customInitializer() {
return new DataSourceScriptDatabaseInitializer(null, new DatabaseInitializationSettings()) {
@Override
protected void runScripts(Scripts scripts) {
// No-op
}
@Override
protected boolean isEmbeddedDatabase() {
return true;
}
};
}
}
@Configuration(proxyBeanMethods = false)
@DependsOnDatabaseInitialization
static class DependsOnInitializedDatabaseConfiguration {
DependsOnInitializedDatabaseConfiguration() {
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.lang.management.ManagementFactory;
import java.util.Set;
import java.util.UUID;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceJmxConfiguration}.
*
* @author Stephane Nicoll
* @author Tadaya Tsuyukubo
*/
class DataSourceJmxConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.url=jdbc:hsqldb:mem:test-" + UUID.randomUUID())
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, DataSourceAutoConfiguration.class));
@Test
void hikariAutoConfiguredCanUseRegisterMBeans() {
String poolName = UUID.randomUUID().toString();
this.contextRunner
.withPropertyValues("spring.jmx.enabled=true", "spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.datasource.name=" + poolName, "spring.datasource.hikari.register-mbeans=true")
.run((context) -> {
assertThat(context).hasSingleBean(HikariDataSource.class);
HikariDataSource hikariDataSource = context.getBean(HikariDataSource.class);
assertThat(hikariDataSource.isRegisterMbeans()).isTrue();
// Ensure that the pool has been initialized, triggering MBean
// registration
hikariDataSource.getConnection().close();
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
validateHikariMBeansRegistration(mBeanServer, poolName, true);
});
}
@Test
void hikariAutoConfiguredWithoutDataSourceName() throws MalformedObjectNameException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectInstance> existingInstances = mBeanServer.queryMBeans(new ObjectName("com.zaxxer.hikari:type=*"),
null);
this.contextRunner
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.datasource.hikari.register-mbeans=true")
.run((context) -> {
assertThat(context).hasSingleBean(HikariDataSource.class);
HikariDataSource hikariDataSource = context.getBean(HikariDataSource.class);
assertThat(hikariDataSource.isRegisterMbeans()).isTrue();
// Ensure that the pool has been initialized, triggering MBean
// registration
hikariDataSource.getConnection().close();
// We can't rely on the number of MBeans so we're checking that the
// pool and pool config MBeans were registered
assertThat(mBeanServer.queryMBeans(new ObjectName("com.zaxxer.hikari:type=*"), null))
.hasSize(existingInstances.size() + 2);
});
}
@Test
void hikariAutoConfiguredUsesJmxFlag() {
String poolName = UUID.randomUUID().toString();
this.contextRunner
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.jmx.enabled=false", "spring.datasource.name=" + poolName,
"spring.datasource.hikari.register-mbeans=true")
.run((context) -> {
assertThat(context).hasSingleBean(HikariDataSource.class);
HikariDataSource hikariDataSource = context.getBean(HikariDataSource.class);
assertThat(hikariDataSource.isRegisterMbeans()).isTrue();
// Ensure that the pool has been initialized, triggering MBean
// registration
hikariDataSource.getConnection().close();
// Hikari can still register mBeans
validateHikariMBeansRegistration(ManagementFactory.getPlatformMBeanServer(), poolName, true);
});
}
@Test
void hikariProxiedCanUseRegisterMBeans() {
String poolName = UUID.randomUUID().toString();
this.contextRunner.withUserConfiguration(DataSourceProxyConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true", "spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.datasource.name=" + poolName, "spring.datasource.hikari.register-mbeans=true")
.run((context) -> {
assertThat(context).hasSingleBean(javax.sql.DataSource.class);
HikariDataSource hikariDataSource = context.getBean(javax.sql.DataSource.class)
.unwrap(HikariDataSource.class);
assertThat(hikariDataSource.isRegisterMbeans()).isTrue();
// Ensure that the pool has been initialized, triggering MBean
// registration
hikariDataSource.getConnection().close();
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
validateHikariMBeansRegistration(mBeanServer, poolName, true);
});
}
private void validateHikariMBeansRegistration(MBeanServer mBeanServer, String poolName, boolean expected)
throws MalformedObjectNameException {
assertThat(mBeanServer.isRegistered(new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")")))
.isEqualTo(expected);
assertThat(mBeanServer.isRegistered(new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")")))
.isEqualTo(expected);
}
@Test
void tomcatDoesNotExposeMBeanPoolByDefault() {
this.contextRunner.withPropertyValues("spring.datasource.type=" + DataSource.class.getName())
.run((context) -> assertThat(context).doesNotHaveBean(ConnectionPool.class));
}
@Test
void tomcatAutoConfiguredCanExposeMBeanPool() {
this.contextRunner
.withPropertyValues("spring.jmx.enabled=true", "spring.datasource.type=" + DataSource.class.getName(),
"spring.datasource.tomcat.jmx-enabled=true")
.run((context) -> {
assertThat(context).hasBean("dataSourceMBean");
assertThat(context).hasSingleBean(ConnectionPool.class);
assertThat(context.getBean(DataSourceProxy.class).createPool().getJmxPool())
.isSameAs(context.getBean(ConnectionPool.class));
});
}
@Test
void tomcatProxiedCanExposeMBeanPool() {
this.contextRunner.withUserConfiguration(DataSourceProxyConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true", "spring.datasource.type=" + DataSource.class.getName(),
"spring.datasource.tomcat.jmx-enabled=true")
.run((context) -> {
assertThat(context).hasBean("dataSourceMBean");
assertThat(context).getBean("dataSourceMBean").isInstanceOf(ConnectionPool.class);
});
}
@Test
void tomcatDelegateCanExposeMBeanPool() {
this.contextRunner.withUserConfiguration(DataSourceDelegateConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true", "spring.datasource.type=" + DataSource.class.getName(),
"spring.datasource.tomcat.jmx-enabled=true")
.run((context) -> {
assertThat(context).hasBean("dataSourceMBean");
assertThat(context).getBean("dataSourceMBean").isInstanceOf(ConnectionPool.class);
});
}
@Configuration(proxyBeanMethods = false)
static class DataSourceProxyConfiguration {
@Bean
static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() {
return new DataSourceBeanPostProcessor();
}
}
static class DataSourceBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof javax.sql.DataSource) {
return new ProxyFactory(bean).getProxy();
}
return bean;
}
}
@Configuration(proxyBeanMethods = false)
static class DataSourceDelegateConfiguration {
@Bean
static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() {
return new DataSourceBeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return (bean instanceof javax.sql.DataSource)
? new DelegatingDataSource((javax.sql.DataSource) bean) : bean;
}
};
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test that a {@link DataSource} can be exposed as JSON for actuator endpoints.
*
* @author Dave Syer
*/
class DataSourceJsonSerializationTests {
@Test
void serializerFactory() throws Exception {
DataSource dataSource = new DataSource();
SerializerFactory factory = BeanSerializerFactory.instance
.withSerializerModifier(new GenericSerializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerFactory(factory);
String value = mapper.writeValueAsString(dataSource);
assertThat(value).contains("\"url\":");
}
@Test
void serializerWithMixin() throws Exception {
DataSource dataSource = new DataSource();
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(DataSource.class, DataSourceJson.class);
String value = mapper.writeValueAsString(dataSource);
assertThat(value).contains("\"url\":");
assertThat(StringUtils.countOccurrencesOf(value, "\"url\"")).isOne();
}
@JsonSerialize(using = TomcatDataSourceSerializer.class)
interface DataSourceJson {
}
static class TomcatDataSourceSerializer extends JsonSerializer<DataSource> {
private final ConversionService conversionService = new DefaultConversionService();
@Override
public void serialize(DataSource value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartObject();
for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(DataSource.class)) {
Method reader = property.getReadMethod();
if (reader != null && property.getWriteMethod() != null
&& this.conversionService.canConvert(String.class, property.getPropertyType())) {
jgen.writeObjectField(property.getName(), ReflectionUtils.invokeMethod(reader, value));
}
}
jgen.writeEndObject();
}
}
static class GenericSerializerModifier extends BeanSerializerModifier {
private final ConversionService conversionService = new DefaultConversionService();
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> result = new ArrayList<>();
for (BeanPropertyWriter writer : beanProperties) {
AnnotatedMethod setter = beanDesc.findMethod("set" + StringUtils.capitalize(writer.getName()),
new Class<?>[] { writer.getType().getRawClass() });
if (setter != null && this.conversionService.canConvert(String.class, writer.getType().getRawClass())) {
result.add(writer);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.context.FilteredClassLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link DataSourceProperties}.
*
* @author Maciej Walkowiak
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Scott Frederick
*/
class DataSourcePropertiesTests {
@Test
void determineDriver() {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl("jdbc:mysql://mydb");
assertThat(properties.getDriverClassName()).isNull();
assertThat(properties.determineDriverClassName()).isEqualTo("com.mysql.cj.jdbc.Driver");
}
@Test
void determineDriverWithExplicitConfig() {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl("jdbc:mysql://mydb");
properties.setDriverClassName("org.hsqldb.jdbcDriver");
assertThat(properties.getDriverClassName()).isEqualTo("org.hsqldb.jdbcDriver");
assertThat(properties.determineDriverClassName()).isEqualTo("org.hsqldb.jdbcDriver");
}
@Test
void determineUrlWithoutGenerateUniqueName() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setGenerateUniqueName(false);
properties.afterPropertiesSet();
assertThat(properties.getUrl()).isNull();
assertThat(properties.determineUrl()).isEqualTo(EmbeddedDatabaseConnection.H2.getUrl("testdb"));
}
@Test
void determineUrlWithNoEmbeddedSupport() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setBeanClassLoader(new FilteredClassLoader("org.h2", "org.apache.derby", "org.hsqldb"));
properties.afterPropertiesSet();
assertThatExceptionOfType(DataSourceProperties.DataSourceBeanCreationException.class)
.isThrownBy(properties::determineUrl)
.withMessageContaining("Failed to determine suitable jdbc url");
}
@Test
void determineUrlWithSpecificEmbeddedConnection() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setGenerateUniqueName(false);
properties.setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection.HSQLDB);
properties.afterPropertiesSet();
assertThat(properties.determineUrl()).isEqualTo(EmbeddedDatabaseConnection.HSQLDB.getUrl("testdb"));
}
@Test
void whenEmbeddedConnectionIsNoneAndNoUrlIsConfiguredThenDetermineUrlThrows() {
DataSourceProperties properties = new DataSourceProperties();
properties.setGenerateUniqueName(false);
properties.setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection.NONE);
assertThatExceptionOfType(DataSourceProperties.DataSourceBeanCreationException.class)
.isThrownBy(properties::determineUrl)
.withMessageContaining("Failed to determine suitable jdbc url");
}
@Test
void determineUrlWithExplicitConfig() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl("jdbc:mysql://mydb");
properties.afterPropertiesSet();
assertThat(properties.getUrl()).isEqualTo("jdbc:mysql://mydb");
assertThat(properties.determineUrl()).isEqualTo("jdbc:mysql://mydb");
}
@Test
void determineUrlWithGenerateUniqueName() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.afterPropertiesSet();
assertThat(properties.determineUrl()).isEqualTo(properties.determineUrl());
DataSourceProperties properties2 = new DataSourceProperties();
properties2.setGenerateUniqueName(true);
properties2.afterPropertiesSet();
assertThat(properties.determineUrl()).isNotEqualTo(properties2.determineUrl());
}
@Test
void determineUsername() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.afterPropertiesSet();
assertThat(properties.getUsername()).isNull();
assertThat(properties.determineUsername()).isEqualTo("sa");
}
@Test
void determineUsernameWhenEmpty() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUsername("");
properties.afterPropertiesSet();
assertThat(properties.getUsername()).isEmpty();
assertThat(properties.determineUsername()).isEqualTo("sa");
}
@Test
void determineUsernameWhenNull() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUsername(null);
properties.afterPropertiesSet();
assertThat(properties.getUsername()).isNull();
assertThat(properties.determineUsername()).isEqualTo("sa");
}
@Test
void determineUsernameWithExplicitConfig() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUsername("foo");
properties.afterPropertiesSet();
assertThat(properties.getUsername()).isEqualTo("foo");
assertThat(properties.determineUsername()).isEqualTo("foo");
}
@Test
void determineUsernameWithNonEmbeddedUrl() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl("jdbc:h2:~/test");
properties.afterPropertiesSet();
assertThat(properties.getPassword()).isNull();
assertThat(properties.determineUsername()).isNull();
}
@Test
void determinePassword() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.afterPropertiesSet();
assertThat(properties.getPassword()).isNull();
assertThat(properties.determinePassword()).isEmpty();
}
@Test
void determinePasswordWithExplicitConfig() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setPassword("bar");
properties.afterPropertiesSet();
assertThat(properties.getPassword()).isEqualTo("bar");
assertThat(properties.determinePassword()).isEqualTo("bar");
}
@Test
void determinePasswordWithNonEmbeddedUrl() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setUrl("jdbc:h2:~/test");
properties.afterPropertiesSet();
assertThat(properties.getPassword()).isNull();
assertThat(properties.determinePassword()).isNull();
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.util.UUID;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.TransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DataSourceTransactionManagerAutoConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Davin Byeon
* @author Moritz Halbritter
*/
class DataSourceTransactionManagerAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TransactionAutoConfiguration.class,
TransactionManagerCustomizationAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class))
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:test-" + UUID.randomUUID());
@Test
void transactionManagerWithoutDataSourceIsNotConfigured() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(TransactionManager.class));
}
@Test
void transactionManagerWithExistingDataSourceIsConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasSingleBean(TransactionManager.class).hasSingleBean(JdbcTransactionManager.class);
assertThat(context.getBean(JdbcTransactionManager.class).getDataSource())
.isSameAs(context.getBean(DataSource.class));
});
}
@Test
void transactionManagerWithCustomizationIsConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.transaction.default-timeout=1m",
"spring.transaction.rollback-on-commit-failure=true")
.run((context) -> {
assertThat(context).hasSingleBean(TransactionManager.class).hasSingleBean(JdbcTransactionManager.class);
JdbcTransactionManager transactionManager = context.getBean(JdbcTransactionManager.class);
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(60);
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
});
}
@Test
void transactionManagerWithExistingTransactionManagerIsNotOverridden() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withBean("myTransactionManager", TransactionManager.class, () -> mock(TransactionManager.class))
.run((context) -> assertThat(context).hasSingleBean(DataSource.class)
.hasSingleBean(TransactionManager.class)
.hasBean("myTransactionManager"));
}
@Test // gh-24321
void transactionManagerWithDaoExceptionTranslationDisabled() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.dao.exceptiontranslation.enabled=false")
.run((context) -> assertThat(context.getBean(TransactionManager.class))
.isExactlyInstanceOf(DataSourceTransactionManager.class));
}
@Test // gh-24321
void transactionManagerWithDaoExceptionTranslationEnabled() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.dao.exceptiontranslation.enabled=true")
.run((context) -> assertThat(context.getBean(TransactionManager.class))
.isExactlyInstanceOf(JdbcTransactionManager.class));
}
@Test // gh-24321
void transactionManagerWithDaoExceptionTranslationDefault() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.run((context) -> assertThat(context.getBean(TransactionManager.class))
.isExactlyInstanceOf(JdbcTransactionManager.class));
}
@Test
void transactionWithMultipleDataSourcesIsNotConfigured() {
this.contextRunner.withUserConfiguration(MultiDataSourceConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(TransactionManager.class));
}
@Test
void transactionWithMultipleDataSourcesAndPrimaryCandidateIsConfigured() {
this.contextRunner.withUserConfiguration(MultiDataSourceUsingPrimaryConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(TransactionManager.class).hasSingleBean(JdbcTransactionManager.class);
assertThat(context.getBean(JdbcTransactionManager.class).getDataSource())
.isSameAs(context.getBean("test1DataSource"));
});
}
@Test
void shouldNotUseDataSourcePropertiesIfDataSourceIsNotOnTheClasspath() {
this.contextRunner.withClassLoader(new FilteredClassLoader(DataSource.class))
.run((context) -> assertThat(context).doesNotHaveBean(DataSourceProperties.class));
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Dbcp2JdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordUrlAndDriverClassName() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new Dbcp2JdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
assertThat(dataSource.getUserName()).isEqualTo("user-1");
assertThat(dataSource).extracting("password").isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EmbeddedDataSourceConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
*/
class EmbeddedDataSourceConfigurationTests {
private AnnotationConfigApplicationContext context;
@AfterEach
void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
void defaultEmbeddedDatabase() {
this.context = load();
assertThat(this.context.getBean(DataSource.class)).isNotNull();
}
@Test
void generateUniqueName() throws Exception {
this.context = load("spring.datasource.generate-unique-name=true");
try (AnnotationConfigApplicationContext context2 = load("spring.datasource.generate-unique-name=true")) {
DataSource dataSource = this.context.getBean(DataSource.class);
DataSource dataSource2 = context2.getBean(DataSource.class);
assertThat(getDatabaseName(dataSource)).isNotEqualTo(getDatabaseName(dataSource2));
}
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
ResultSet catalogs = connection.getMetaData().getCatalogs();
if (catalogs.next()) {
return catalogs.getString(1);
}
else {
throw new IllegalStateException("Unable to get database name");
}
}
}
private AnnotationConfigApplicationContext load(String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment).applyTo(ctx);
ctx.register(EmbeddedDataSourceConfiguration.class);
ctx.refresh();
return ctx;
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DataSourceAutoConfiguration} with Hikari.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Olga Maciaszek-Sharma
*/
class HikariDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.hikari.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName());
@Test
void testDataSourceExists() {
this.contextRunner.run((context) -> {
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
assertThat(context.getBeansOfType(HikariDataSource.class)).hasSize(1);
});
}
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner
.withPropertyValues(PREFIX + "jdbc-url=jdbc:foo//bar/spam", "spring.datasource.hikari.max-lifetime=1234")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getJdbcUrl()).isEqualTo("jdbc:foo//bar/spam");
assertThat(ds.getMaxLifetime()).isEqualTo(1234);
});
}
@Test
void testDataSourceGenericPropertiesOverridden() {
this.contextRunner
.withPropertyValues(PREFIX + "data-source-properties.dataSourceClassName=org.h2.JDBCDataSource")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName"))
.isEqualTo("org.h2.JDBCDataSource");
});
}
@Test
@SuppressWarnings("resource")
@ClassPathExclusions({ "h2-*.jar", "hsqldb-*.jar" })
void configureDataSourceClassNameWithNoEmbeddedDatabaseAvailable() {
this.contextRunner
.withPropertyValues("spring.datasource.url=jdbc:example//",
"spring.datasource.hikari.data-source-class-name=" + MockDataSource.class.getName())
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceClassName()).isEqualTo(MockDataSource.class.getName());
assertThatNoException().isThrownBy(() -> ds.getConnection().close());
});
}
@Test
@SuppressWarnings("resource")
void configureDataSourceClassNameToOverrideUseOfAnEmbeddedDatabase() {
this.contextRunner
.withPropertyValues("spring.datasource.url=jdbc:example//",
"spring.datasource.hikari.data-source-class-name=" + MockDataSource.class.getName())
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceClassName()).isEqualTo(MockDataSource.class.getName());
assertThatNoException().isThrownBy(() -> ds.getConnection().close());
});
}
@Test
void testDataSourceDefaultsPreserved() {
this.contextRunner.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getMaxLifetime()).isEqualTo(1800000);
});
}
@Test
void nameIsAliasedToPoolName() {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getPoolName()).isEqualTo("myDS");
});
}
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "pool-name=myHikariDS")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getPoolName()).isEqualTo("myHikariDS");
});
}
@Test
void usesCustomConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(HikariDataSource.class))
.satisfies((hikari) -> {
assertThat(hikari.getUsername()).isEqualTo("user-1");
assertThat(hikari.getPassword()).isEqualTo("password-1");
assertThat(hikari.getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(hikari.getJdbcUrl())
.isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
});
});
}
@Test
@ClassPathOverrides("org.crac:crac:1.3.0")
void whenCheckpointRestoreIsAvailableHikariAutoConfigRegistersLifecycleBean() {
this.contextRunner.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName())
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
}
@Test
@ClassPathOverrides("org.crac:crac:1.3.0")
void whenCheckpointRestoreIsAvailableAndDataSourceHasBeenWrappedHikariAutoConfigRegistersLifecycleBean() {
this.contextRunner.withUserConfiguration(DataSourceWrapperConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
}
@Test
void whenCheckpointRestoreIsNotAvailableHikariAutoConfigDoesNotRegisterLifecycleBean() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(HikariCheckpointRestoreLifecycle.class));
}
@Test
@ClassPathOverrides("org.crac:crac:1.3.0")
void whenCheckpointRestoreIsAvailableAndDataSourceIsFromUserConfigurationHikariAutoConfigRegistersLifecycleBean() {
this.contextRunner.withUserConfiguration(UserDataSourceConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(HikariCheckpointRestoreLifecycle.class));
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class DataSourceWrapperConfiguration {
@Bean
static BeanPostProcessor dataSourceWrapper() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource dataSource) {
return new DelegatingDataSource(dataSource);
}
return bean;
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class UserDataSourceConfiguration {
@Bean
DataSource dataSource() {
return DataSourceBuilder.create()
.driverClassName("org.postgresql.Driver")
.url("jdbc:postgresql://localhost:5432/database")
.username("user")
.password("password")
.build();
}
}
public static class MockDataSource implements DataSource {
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection() throws SQLException {
return mock(Connection.class);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return -1;
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HikariDriverConfigurationFailureAnalyzer}.
*
* @author Stephane Nicoll
*/
class HikariDriverConfigurationFailureAnalyzerTests {
@Test
@WithResource(name = "schema.sql", content = "")
void failureAnalysisIsPerformed() {
FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class);
assertThat(failureAnalysis).isNotNull();
assertThat(failureAnalysis.getDescription())
.isEqualTo("Configuration of the Hikari connection pool failed: 'dataSourceClassName' is not supported.");
assertThat(failureAnalysis.getAction()).contains("Spring Boot auto-configures only a driver");
}
@Test
void unrelatedIllegalStateExceptionIsSkipped() {
FailureAnalysis failureAnalysis = new HikariDriverConfigurationFailureAnalyzer()
.analyze(new RuntimeException("foo", new IllegalStateException("bar")));
assertThat(failureAnalysis).isNull();
}
private FailureAnalysis performAnalysis(Class<?> configuration) {
BeanCreationException failure = createFailure(configuration);
assertThat(failure).isNotNull();
return new HikariDriverConfigurationFailureAnalyzer().analyze(failure);
}
private BeanCreationException createFailure(Class<?> configuration) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues
.of("spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.datasource.hikari.data-source-class-name=com.example.Foo", "spring.sql.init.mode=always")
.applyTo(context);
context.register(configuration);
try {
context.refresh();
context.close();
return null;
}
catch (BeanCreationException ex) {
return ex;
}
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration({ DataSourceAutoConfiguration.class, DataSourceInitializationAutoConfiguration.class })
static class TestConfiguration {
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HikariJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName(DatabaseDriver.H2.getDriverClassName());
new HikariJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getJdbcUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
@Test
void toleratesConnectionDetailsWithNullDriverClassName() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(DatabaseDriver.H2.getDriverClassName());
JdbcConnectionDetails connectionDetails = mock(JdbcConnectionDetails.class);
new HikariJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource, connectionDetails);
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.H2.getDriverClassName());
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.JdbcClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JdbcClientAutoConfiguration}.
*
* @author Stephane Nicoll
*/
class JdbcClientAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.generate-unique-name=true")
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
JdbcClientAutoConfiguration.class));
@Test
void jdbcClientWhenNoAvailableJdbcTemplateIsNotCreated() {
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(DataSourceAutoConfiguration.class, JdbcClientAutoConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean(JdbcClient.class));
}
@Test
void jdbcClientWhenExistingJdbcTemplateIsCreated() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(JdbcClient.class);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = context.getBean(NamedParameterJdbcTemplate.class);
assertThat(namedParameterJdbcTemplate.getJdbcOperations()).isEqualTo(context.getBean(JdbcOperations.class));
});
}
@Test
void jdbcClientWithCustomJdbcClientIsNotCreated() {
this.contextRunner.withBean("customJdbcClient", JdbcClient.class, () -> mock(JdbcClient.class))
.run((context) -> {
assertThat(context).hasSingleBean(JdbcClient.class);
assertThat(context.getBean(JdbcClient.class)).isEqualTo(context.getBean("customJdbcClient"));
});
}
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JdbcTemplateAutoConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Dan Zheng
*/
class JdbcTemplateAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.generate-unique-name=true")
.withConfiguration(
AutoConfigurations.of(DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class));
@Test
void testJdbcTemplateExists() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(JdbcOperations.class);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
assertThat(jdbcTemplate.getDataSource()).isEqualTo(context.getBean(DataSource.class));
assertThat(jdbcTemplate.isIgnoreWarnings()).isEqualTo(true);
assertThat(jdbcTemplate.getFetchSize()).isEqualTo(-1);
assertThat(jdbcTemplate.getQueryTimeout()).isEqualTo(-1);
assertThat(jdbcTemplate.getMaxRows()).isEqualTo(-1);
assertThat(jdbcTemplate.isSkipResultsProcessing()).isEqualTo(false);
assertThat(jdbcTemplate.isSkipUndeclaredResults()).isEqualTo(false);
assertThat(jdbcTemplate.isResultsMapCaseInsensitive()).isEqualTo(false);
});
}
@Test
void testJdbcTemplateWithCustomProperties() {
this.contextRunner
.withPropertyValues("spring.jdbc.template.ignore-warnings:false", "spring.jdbc.template.fetch-size:100",
"spring.jdbc.template.query-timeout:60", "spring.jdbc.template.max-rows:1000",
"spring.jdbc.template.skip-results-processing:true",
"spring.jdbc.template.skip-undeclared-results:true",
"spring.jdbc.template.results-map-case-insensitive:true")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcOperations.class);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
assertThat(jdbcTemplate.getDataSource()).isNotNull();
assertThat(jdbcTemplate.isIgnoreWarnings()).isEqualTo(false);
assertThat(jdbcTemplate.getFetchSize()).isEqualTo(100);
assertThat(jdbcTemplate.getQueryTimeout()).isEqualTo(60);
assertThat(jdbcTemplate.getMaxRows()).isEqualTo(1000);
assertThat(jdbcTemplate.isSkipResultsProcessing()).isEqualTo(true);
assertThat(jdbcTemplate.isSkipUndeclaredResults()).isEqualTo(true);
assertThat(jdbcTemplate.isResultsMapCaseInsensitive()).isEqualTo(true);
});
}
@Test
void testJdbcTemplateExistsWithCustomDataSource() {
this.contextRunner.withUserConfiguration(TestDataSourceConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(JdbcOperations.class);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
assertThat(jdbcTemplate.getDataSource()).isEqualTo(context.getBean("customDataSource"));
});
}
@Test
void testNamedParameterJdbcTemplateExists() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(NamedParameterJdbcOperations.class);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = context.getBean(NamedParameterJdbcTemplate.class);
assertThat(namedParameterJdbcTemplate.getJdbcOperations()).isEqualTo(context.getBean(JdbcOperations.class));
});
}
@Test
void testMultiDataSource() {
this.contextRunner.withUserConfiguration(MultiDataSourceConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(JdbcOperations.class);
assertThat(context).doesNotHaveBean(NamedParameterJdbcOperations.class);
});
}
@Test
void testMultiJdbcTemplate() {
this.contextRunner.withUserConfiguration(MultiJdbcTemplateConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(NamedParameterJdbcOperations.class));
}
@Test
void testMultiDataSourceUsingPrimary() {
this.contextRunner.withUserConfiguration(MultiDataSourceUsingPrimaryConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(JdbcOperations.class);
assertThat(context).hasSingleBean(NamedParameterJdbcOperations.class);
assertThat(context.getBean(JdbcTemplate.class).getDataSource())
.isEqualTo(context.getBean("test1DataSource"));
});
}
@Test
void testMultiJdbcTemplateUsingPrimary() {
this.contextRunner.withUserConfiguration(MultiJdbcTemplateUsingPrimaryConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(NamedParameterJdbcOperations.class);
assertThat(context.getBean(NamedParameterJdbcTemplate.class).getJdbcOperations())
.isEqualTo(context.getBean("test1Template"));
});
}
@Test
void testExistingCustomJdbcTemplate() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(JdbcOperations.class);
assertThat(context.getBean(JdbcOperations.class)).isEqualTo(context.getBean("customJdbcOperations"));
});
}
@Test
void testExistingCustomNamedParameterJdbcTemplate() {
this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(NamedParameterJdbcOperations.class);
assertThat(context.getBean(NamedParameterJdbcOperations.class))
.isEqualTo(context.getBean("customNamedParameterJdbcOperations"));
});
}
@Test
@WithResource(name = "schema.sql", content = """
CREATE TABLE BAR (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(30)
);
""")
@WithResource(name = "data.sql", content = "INSERT INTO BAR VALUES (1, 'Andy');")
void testDependencyToScriptBasedDataSourceInitialization() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceInitializationAutoConfiguration.class))
.withUserConfiguration(DataSourceInitializationValidator.class)
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.getBean(DataSourceInitializationValidator.class).count).isOne();
});
}
@Test
void shouldConfigureJdbcTemplateWithSQLExceptionTranslatorIfPresent() {
SQLStateSQLExceptionTranslator sqlExceptionTranslator = new SQLStateSQLExceptionTranslator();
this.contextRunner.withBean(SQLExceptionTranslator.class, () -> sqlExceptionTranslator).run((context) -> {
assertThat(context).hasSingleBean(JdbcTemplate.class);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
assertThat(jdbcTemplate.getExceptionTranslator()).isSameAs(sqlExceptionTranslator);
});
}
@Test
void shouldNotConfigureJdbcTemplateWithSQLExceptionTranslatorIfNotUnique() {
SQLStateSQLExceptionTranslator sqlExceptionTranslator1 = new SQLStateSQLExceptionTranslator();
SQLStateSQLExceptionTranslator sqlExceptionTranslator2 = new SQLStateSQLExceptionTranslator();
this.contextRunner
.withBean("sqlExceptionTranslator1", SQLExceptionTranslator.class, () -> sqlExceptionTranslator1)
.withBean("sqlExceptionTranslator2", SQLExceptionTranslator.class, () -> sqlExceptionTranslator2)
.run((context) -> {
assertThat(context).hasSingleBean(JdbcTemplate.class);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
assertThat(jdbcTemplate.getExceptionTranslator()).isNotSameAs(sqlExceptionTranslator1)
.isNotSameAs(sqlExceptionTranslator2);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomConfiguration {
@Bean
JdbcOperations customJdbcOperations(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
NamedParameterJdbcOperations customNamedParameterJdbcOperations(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}
@Configuration(proxyBeanMethods = false)
static class TestDataSourceConfiguration {
@Bean
DataSource customDataSource() {
return new TestDataSource();
}
}
@Configuration(proxyBeanMethods = false)
static class MultiJdbcTemplateConfiguration {
@Bean
JdbcTemplate test1Template() {
return mock(JdbcTemplate.class);
}
@Bean
JdbcTemplate test2Template() {
return mock(JdbcTemplate.class);
}
}
@Configuration(proxyBeanMethods = false)
static class MultiJdbcTemplateUsingPrimaryConfiguration {
@Bean
@Primary
JdbcTemplate test1Template() {
return mock(JdbcTemplate.class);
}
@Bean
JdbcTemplate test2Template() {
return mock(JdbcTemplate.class);
}
}
static class DataSourceInitializationValidator {
private final Integer count;
DataSourceInitializationValidator(JdbcTemplate jdbcTemplate) {
this.count = jdbcTemplate.queryForObject("SELECT COUNT(*) from BAR", Integer.class);
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "db/changelog/db.changelog-city.yaml", content = """
databaseChangeLog:
- changeSet:
id: 1
author: dsyer
changes:
- createSequence:
sequenceName: city_seq
incrementBy: 50
- createTable:
tableName: city
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(50)
constraints:
nullable: false
""")
@interface WithDbChangelogCityYamlResource {
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.util.Set;
import javax.naming.Context;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader;
import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JndiDataSourceAutoConfiguration}
*
* @author Andy Wilkinson
*/
class JndiDataSourceAutoConfigurationTests {
private ClassLoader threadContextClassLoader;
private String initialContextFactory;
private AnnotationConfigApplicationContext context;
@BeforeEach
void setupJndi() {
this.initialContextFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, TestableInitialContextFactory.class.getName());
}
@BeforeEach
void setupThreadContextClassLoader() {
this.threadContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(new JndiPropertiesHidingClassLoader(getClass().getClassLoader()));
}
@AfterEach
void close() {
TestableInitialContextFactory.clearAll();
if (this.initialContextFactory != null) {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, this.initialContextFactory);
}
else {
System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
}
if (this.context != null) {
this.context.close();
}
Thread.currentThread().setContextClassLoader(this.threadContextClassLoader);
}
@Test
void dataSourceIsAvailableFromJndi() {
DataSource dataSource = new BasicDataSource();
configureJndi("foo", dataSource);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.jndi-name:foo").applyTo(this.context);
this.context.register(JndiDataSourceAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(DataSource.class)).isEqualTo(dataSource);
}
@SuppressWarnings("unchecked")
@Test
void mbeanDataSourceIsExcludedFromExport() {
DataSource dataSource = new BasicDataSource();
configureJndi("foo", dataSource);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.jndi-name:foo").applyTo(this.context);
this.context.register(JndiDataSourceAutoConfiguration.class, MBeanExporterConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(DataSource.class)).isEqualTo(dataSource);
MBeanExporter exporter = this.context.getBean(MBeanExporter.class);
Set<String> excludedBeans = (Set<String>) ReflectionTestUtils.getField(exporter, "excludedBeans");
assertThat(excludedBeans).containsExactly("dataSource");
}
@SuppressWarnings("unchecked")
@Test
void mbeanDataSourceIsExcludedFromExportByAllExporters() {
DataSource dataSource = new BasicDataSource();
configureJndi("foo", dataSource);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.jndi-name:foo").applyTo(this.context);
this.context.register(JndiDataSourceAutoConfiguration.class, MBeanExporterConfiguration.class,
AnotherMBeanExporterConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(DataSource.class)).isEqualTo(dataSource);
for (MBeanExporter exporter : this.context.getBeansOfType(MBeanExporter.class).values()) {
Set<String> excludedBeans = (Set<String>) ReflectionTestUtils.getField(exporter, "excludedBeans");
assertThat(excludedBeans).containsExactly("dataSource");
}
}
@SuppressWarnings("unchecked")
@Test
void standardDataSourceIsNotExcludedFromExport() {
DataSource dataSource = mock(DataSource.class);
configureJndi("foo", dataSource);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.jndi-name:foo").applyTo(this.context);
this.context.register(JndiDataSourceAutoConfiguration.class, MBeanExporterConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(DataSource.class)).isEqualTo(dataSource);
MBeanExporter exporter = this.context.getBean(MBeanExporter.class);
Set<String> excludedBeans = (Set<String>) ReflectionTestUtils.getField(exporter, "excludedBeans");
assertThat(excludedBeans).isEmpty();
}
private void configureJndi(String name, DataSource dataSource) {
TestableInitialContextFactory.bind(name, dataSource);
}
@Configuration(proxyBeanMethods = false)
static class MBeanExporterConfiguration {
@Bean
MBeanExporter mbeanExporter() {
return new MBeanExporter();
}
}
@Configuration(proxyBeanMethods = false)
static class AnotherMBeanExporterConfiguration {
@Bean
MBeanExporter anotherMbeanExporter() {
return new MBeanExporter();
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for multiple {@link DataSource}.
*
* @author Phillip Webb
* @author Kazuki Shimizu
*/
@Configuration(proxyBeanMethods = false)
class MultiDataSourceConfiguration {
@Bean
DataSource test1DataSource() {
return new TestDataSource("test1", false);
}
@Bean
DataSource test2DataSource() {
return new TestDataSource("test2", false);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* Configuration for multiple {@link DataSource} (one being {@code @Primary}).
*
* @author Phillip Webb
* @author Kazuki Shimizu
*/
@Configuration(proxyBeanMethods = false)
class MultiDataSourceUsingPrimaryConfiguration {
@Bean
@Primary
DataSource test1DataSource() {
return new TestDataSource("test1", false);
}
@Bean
DataSource test2DataSource() {
return new TestDataSource("test2", false);
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.Connection;
import java.time.Duration;
import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
*
* @author Fabio Grassi
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.oracleucp.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + PoolDataSource.class.getName());
@Test
void testDataSourceExists() {
this.contextRunner.run((context) -> {
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
assertThat(connection.isValid(1000)).isTrue();
}
});
}
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner.withPropertyValues(PREFIX + "url=jdbc:foo//bar/spam", PREFIX + "max-idle-time=1234")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
assertThat(ds.getMaxIdleTime()).isEqualTo(1234);
});
}
@Test
void testDataSourceConnectionPropertiesOverridden() {
this.contextRunner.withPropertyValues(PREFIX + "connection-properties.autoCommit=false").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
});
}
@Test
void testDataSourceDefaultsPreserved() {
this.contextRunner.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getInitialPoolSize()).isZero();
assertThat(ds.getMinPoolSize()).isOne();
assertThat(ds.getMaxPoolSize()).isEqualTo(Integer.MAX_VALUE);
assertThat(ds.getInactiveConnectionTimeout()).isZero();
assertThat(ds.getConnectionWaitDuration()).isEqualTo(Duration.ofSeconds(3));
assertThat(ds.getTimeToLiveConnectionTimeout()).isZero();
assertThat(ds.getAbandonedConnectionTimeout()).isZero();
assertThat(ds.getTimeoutCheckInterval()).isEqualTo(30);
assertThat(ds.getFastConnectionFailoverEnabled()).isFalse();
});
}
@Test
void nameIsAliasedToPoolName() {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myDS");
});
}
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner
.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connection-pool-name=myOracleUcpDS")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
});
}
@Test
void usesCustomJdbcConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(PoolDataSourceImpl.class);
PoolDataSourceImpl oracleUcp = (PoolDataSourceImpl) dataSource;
assertThat(oracleUcp.getUser()).isEqualTo("user-1");
assertThat(oracleUcp).extracting("password")
.extracting((o) -> ((OpaqueString) o).get())
.isEqualTo("password-1");
assertThat(oracleUcp.getConnectionFactoryClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
assertThat(oracleUcp.getURL()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OracleUcpJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordUrlAndDriverClassName() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setURL("will-be-overwritten");
dataSource.setUser("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setConnectionFactoryClassName("will-be-overwritten");
new OracleUcpJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getURL()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource).extracting("password")
.extracting((password) -> ((OpaqueString) password).get())
.isEqualTo("password-1");
assertThat(dataSource.getConnectionFactoryClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.UUID;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
/**
* {@link BasicDataSource} used for testing.
*
* @author Phillip Webb
* @author Kazuki Shimizu
* @author Stephane Nicoll
*/
public class TestDataSource extends SimpleDriverDataSource {
/**
* Create an in-memory database with a random name.
*/
public TestDataSource() {
this(false);
}
/**
* Create an in-memory database with a random name.
* @param addTestUser if a test user should be added
*/
public TestDataSource(boolean addTestUser) {
this(UUID.randomUUID().toString(), addTestUser);
}
/**
* Create an in-memory database with the specified name.
* @param name the name of the database
* @param addTestUser if a test user should be added
*/
public TestDataSource(String name, boolean addTestUser) {
setDriverClass(org.hsqldb.jdbc.JDBCDriver.class);
setUrl("jdbc:hsqldb:mem:" + name);
setUsername("sa");
setupDatabase(addTestUser);
setUrl(getUrl() + ";create=false");
}
private void setupDatabase(boolean addTestUser) {
try (Connection connection = getConnection()) {
if (addTestUser) {
connection.prepareStatement("CREATE USER \"test\" password \"secret\" ADMIN").execute();
}
}
catch (SQLException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* {@link JdbcConnectionDetails} used in tests.
*
* @author Moritz Halbritter
*/
class TestJdbcConnectionDetails implements JdbcConnectionDetails {
@Override
public String getJdbcUrl() {
return "jdbc:customdb://customdb.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public String getDriverClassName() {
return DatabaseDriver.POSTGRESQL.getDriverClassName();
}
@Override
public String getXaDataSourceClassName() {
return DatabaseDriver.POSTGRESQL.getXaDataSourceClassName();
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Tests for {@link TomcatDataSourceConfiguration}.
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.tomcat.";
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + org.apache.tomcat.jdbc.pool.DataSource.class.getName());
@BeforeEach
void init() {
TestPropertyValues.of(PREFIX + "initialize:false").applyTo(this.context);
}
@Test
void testDataSourceExists() {
this.context.register(TomcatDataSourceConfiguration.class);
TestPropertyValues.of(PREFIX + "url:jdbc:h2:mem:testdb").applyTo(this.context);
this.context.refresh();
assertThat(this.context.getBean(DataSource.class)).isNotNull();
assertThat(this.context.getBean(org.apache.tomcat.jdbc.pool.DataSource.class)).isNotNull();
}
@Test
void testDataSourcePropertiesOverridden() throws Exception {
this.context.register(TomcatDataSourceConfiguration.class);
TestPropertyValues
.of(PREFIX + "url:jdbc:h2:mem:testdb", PREFIX + "testWhileIdle:true", PREFIX + "testOnBorrow:true",
PREFIX + "testOnReturn:true", PREFIX + "timeBetweenEvictionRunsMillis:10000",
PREFIX + "minEvictableIdleTimeMillis:12345", PREFIX + "maxWait:1234",
PREFIX + "jdbcInterceptors:SlowQueryReport", PREFIX + "validationInterval:9999")
.applyTo(this.context);
this.context.refresh();
org.apache.tomcat.jdbc.pool.DataSource ds = this.context.getBean(org.apache.tomcat.jdbc.pool.DataSource.class);
assertThat(ds.getUrl()).isEqualTo("jdbc:h2:mem:testdb");
assertThat(ds.isTestWhileIdle()).isTrue();
assertThat(ds.isTestOnBorrow()).isTrue();
assertThat(ds.isTestOnReturn()).isTrue();
assertThat(ds.getTimeBetweenEvictionRunsMillis()).isEqualTo(10000);
assertThat(ds.getMinEvictableIdleTimeMillis()).isEqualTo(12345);
assertThat(ds.getMaxWait()).isEqualTo(1234);
assertThat(ds.getValidationInterval()).isEqualTo(9999L);
assertDataSourceHasInterceptors(ds);
}
private void assertDataSourceHasInterceptors(DataSourceProxy ds) throws ClassNotFoundException {
PoolProperties.InterceptorDefinition[] interceptors = ds.getJdbcInterceptorsAsArray();
for (PoolProperties.InterceptorDefinition interceptor : interceptors) {
if (SlowQueryReport.class == interceptor.getInterceptorClass()) {
return;
}
}
fail("SlowQueryReport interceptor should have been set.");
}
@Test
void testDataSourceDefaultsPreserved() {
this.context.register(TomcatDataSourceConfiguration.class);
TestPropertyValues.of(PREFIX + "url:jdbc:h2:mem:testdb").applyTo(this.context);
this.context.refresh();
org.apache.tomcat.jdbc.pool.DataSource ds = this.context.getBean(org.apache.tomcat.jdbc.pool.DataSource.class);
assertThat(ds.getTimeBetweenEvictionRunsMillis()).isEqualTo(5000);
assertThat(ds.getMinEvictableIdleTimeMillis()).isEqualTo(60000);
assertThat(ds.getMaxWait()).isEqualTo(30000);
assertThat(ds.getValidationInterval()).isEqualTo(3000L);
}
@Test
void usesCustomJdbcConnectionDetailsWhenDefined() {
this.contextRunner.withBean(JdbcConnectionDetails.class, TestJdbcConnectionDetails::new)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
org.apache.tomcat.jdbc.pool.DataSource tomcat = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
assertThat(tomcat.getPoolProperties().getUsername()).isEqualTo("user-1");
assertThat(tomcat.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(tomcat.getPoolProperties().getDriverClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
assertThat(tomcat.getPoolProperties().getUrl())
.isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
});
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@EnableMBeanExport
static class TomcatDataSourceConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.tomcat")
DataSource dataSource() {
return DataSourceBuilder.create().type(org.apache.tomcat.jdbc.pool.DataSource.class).build();
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TomcatJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordUrlAndDriverClassName() {
DataSource dataSource = new DataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new TomcatJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:customdb://customdb.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(dataSource.getPoolProperties().getDriverClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.autoconfigure;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import com.ibm.db2.jcc.DB2XADataSource;
import org.hsqldb.jdbc.pool.JDBCXADataSource;
import org.junit.jupiter.api.Test;
import org.postgresql.xa.PGXADataSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link XADataSourceAutoConfiguration}.
*
* @author Phillip Webb
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
class XADataSourceAutoConfigurationTests {
@Test
void wrapExistingXaDataSource() {
ApplicationContext context = createContext(WrapExisting.class);
context.getBean(DataSource.class);
XADataSource source = context.getBean(XADataSource.class);
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
assertThat(wrapper.getXaDataSource()).isEqualTo(source);
}
@Test
void createFromUrl() {
ApplicationContext context = createContext(FromProperties.class, "spring.datasource.url:jdbc:hsqldb:mem:test",
"spring.datasource.username:un");
context.getBean(DataSource.class);
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();
assertThat(dataSource.getUrl()).isEqualTo("jdbc:hsqldb:mem:test");
assertThat(dataSource.getUser()).isEqualTo("un");
}
@Test
void createNonEmbeddedFromXAProperties() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class)
.withClassLoader(new FilteredClassLoader("org.h2.Driver", "org.hsqldb.jdbcDriver"))
.withPropertyValues("spring.datasource.xa.data-source-class-name:com.ibm.db2.jcc.DB2XADataSource",
"spring.datasource.xa.properties.user:test", "spring.datasource.xa.properties.password:secret")
.run((context) -> {
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
XADataSource xaDataSource = wrapper.getXaDataSource();
assertThat(xaDataSource).isInstanceOf(DB2XADataSource.class);
});
}
@Test
void createFromClass() throws Exception {
ApplicationContext context = createContext(FromProperties.class,
"spring.datasource.xa.data-source-class-name:org.hsqldb.jdbc.pool.JDBCXADataSource",
"spring.datasource.xa.properties.login-timeout:123");
context.getBean(DataSource.class);
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();
assertThat(dataSource.getLoginTimeout()).isEqualTo(123);
}
@Test
void definesPropertiesBasedConnectionDetailsByDefault() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class)
.run((context) -> assertThat(context).hasSingleBean(PropertiesJdbcConnectionDetails.class));
}
@Test
void shouldUseCustomConnectionDetailsWhenDefined() {
JdbcConnectionDetails connectionDetails = mock(JdbcConnectionDetails.class);
given(connectionDetails.getUsername()).willReturn("user-1");
given(connectionDetails.getPassword()).willReturn("password-1");
given(connectionDetails.getJdbcUrl()).willReturn("jdbc:postgresql://postgres.example.com:12345/database-1");
given(connectionDetails.getDriverClassName()).willReturn(DatabaseDriver.POSTGRESQL.getDriverClassName());
given(connectionDetails.getXaDataSourceClassName())
.willReturn(DatabaseDriver.POSTGRESQL.getXaDataSourceClassName());
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class)
.withBean(JdbcConnectionDetails.class, () -> connectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class)
.doesNotHaveBean(PropertiesJdbcConnectionDetails.class);
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
PGXADataSource dataSource = (PGXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();
assertThat(dataSource.getUrl()).startsWith("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
});
}
private ApplicationContext createContext(Class<?> configuration, String... env) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(env).applyTo(context);
context.register(configuration, XADataSourceAutoConfiguration.class);
context.refresh();
return context;
}
@Configuration(proxyBeanMethods = false)
static class WrapExisting {
@Bean
MockXADataSourceWrapper wrapper() {
return new MockXADataSourceWrapper();
}
@Bean
XADataSource xaDataSource() {
return mock(XADataSource.class);
}
}
@Configuration(proxyBeanMethods = false)
static class FromProperties {
@Bean
MockXADataSourceWrapper wrapper() {
return new MockXADataSourceWrapper();
}
}
static class MockXADataSourceWrapper implements XADataSourceWrapper {
private XADataSource dataSource;
@Override
public DataSource wrapDataSource(XADataSource dataSource) {
this.dataSource = dataSource;
return mock(DataSource.class);
}
XADataSource getXaDataSource() {
return this.dataSource;
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Collections;
import java.util.UUID;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.datasource.init.ScriptStatementFailedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link DataSourceScriptDatabaseInitializer}.
*
* @author Andy Wilkinson
*/
class DataSourceScriptDatabaseInitializerTests
extends AbstractScriptDatabaseInitializerTests<DataSourceScriptDatabaseInitializer> {
private final HikariDataSource embeddedDataSource = DataSourceBuilder.create()
.type(HikariDataSource.class)
.url("jdbc:h2:mem:" + UUID.randomUUID())
.build();
private final HikariDataSource standaloneDataSource = DataSourceBuilder.create()
.type(HikariDataSource.class)
.url("jdbc:h2:file:"
+ new BuildOutput(DataSourceScriptDatabaseInitializerTests.class).getRootLocation().getAbsolutePath()
+ "/" + UUID.randomUUID())
.build();
@AfterEach
void closeDataSource() {
this.embeddedDataSource.close();
this.standaloneDataSource.close();
}
@Test
void whenDatabaseIsInaccessibleThenItIsAssumedNotToBeEmbedded() {
DataSourceScriptDatabaseInitializer initializer = new DataSourceScriptDatabaseInitializer(
new HikariDataSource(), new DatabaseInitializationSettings());
assertThat(initializer.isEmbeddedDatabase()).isFalse();
}
@Test
@WithDataSqlResource
void whenCustomizeIsOverriddenThenDatabasePopulatorIsConfiguredAccordingly() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setContinueOnError(true);
settings.setDataLocations(Collections.singletonList("data.sql"));
DataSourceScriptDatabaseInitializer initializer = new DataSourceScriptDatabaseInitializer(
this.embeddedDataSource, settings) {
@Override
protected void customize(ResourceDatabasePopulator populator) {
assertThat(populator).hasFieldOrPropertyWithValue("continueOnError", true);
populator.setContinueOnError(false);
}
};
assertThatExceptionOfType(ScriptStatementFailedException.class).isThrownBy(initializer::initializeDatabase);
}
@Override
protected DataSourceScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.embeddedDataSource, settings);
}
@Override
protected DataSourceScriptDatabaseInitializer createStandaloneDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.standaloneDataSource, settings);
}
@Override
protected int numberOfEmbeddedRows(String sql) {
return numberOfRows(this.embeddedDataSource, sql);
}
@Override
protected int numberOfStandaloneRows(String sql) {
return numberOfRows(this.standaloneDataSource, sql);
}
private int numberOfRows(DataSource dataSource, String sql) {
return new JdbcTemplate(dataSource).queryForObject(sql, Integer.class);
}
@Override
protected void assertDatabaseAccessed(boolean accessed, DataSourceScriptDatabaseInitializer initializer) {
assertThat(((HikariDataSource) initializer.getDataSource()).isRunning()).isEqualTo(accessed);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PlatformPlaceholderDatabaseDriverResolver}
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
class PlatformPlaceholderDatabaseDriverResolverTests {
@Test
void resolveAllWithPlatformWhenThereAreNoValuesShouldReturnEmptyList() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll("test")).isEmpty();
}
@Test
void resolveAllWithPlatformWhenValueDoesNotContainPlaceholderShouldReturnValueUnchanged() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll("test", "schema.sql"))
.containsExactly("schema.sql");
}
@Test
void resolveAllWithPlatformWhenValuesContainPlaceholdersShouldReturnValuesWithPlaceholdersReplaced() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll("postgresql", "schema.sql",
"schema-@@platform@@.sql", "data-@@platform@@.sql"))
.containsExactly("schema.sql", "schema-postgresql.sql", "data-postgresql.sql");
}
@Test
void resolveAllWithDataSourceWhenThereAreNoValuesShouldReturnEmptyList() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll(mock(DataSource.class))).isEmpty();
}
@Test
void resolveAllWithDataSourceWhenValueDoesNotContainPlaceholderShouldReturnValueUnchanged() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll(mock(DataSource.class), "schema.sql"))
.containsExactly("schema.sql");
}
@Test
void resolveAllWithDataSourceWhenValueDoesNotContainPlaceholderShouldNotInteractWithDataSource() {
DataSource dataSource = mock(DataSource.class);
new PlatformPlaceholderDatabaseDriverResolver().resolveAll(dataSource, "schema.sql");
then(dataSource).shouldHaveNoInteractions();
}
@Test
void resolveAllWithFailingDataSourceWhenValuesContainPlaceholdersShouldThrowNestedCause() throws SQLException {
DataSource dataSource = mock(DataSource.class);
given(dataSource.getConnection()).willThrow(new IllegalStateException("Test: invalid password"));
assertThatIllegalStateException()
.isThrownBy(() -> new PlatformPlaceholderDatabaseDriverResolver().resolveAll(dataSource, "schema.sql",
"schema-@@platform@@.sql", "data-@@platform@@.sql"))
.withMessage("Failed to determine DatabaseDriver")
.withStackTraceContaining("Test: invalid password");
}
@Test
void resolveAllWithDataSourceWhenValuesContainPlaceholdersShouldReturnValuesWithPlaceholdersReplaced()
throws SQLException {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll(dataSourceWithProductName("PostgreSQL"),
"schema.sql", "schema-@@platform@@.sql", "data-@@platform@@.sql"))
.containsExactly("schema.sql", "schema-postgresql.sql", "data-postgresql.sql");
}
@Test
void resolveAllWithDataSourceWhenDriverMappingsAreCustomizedShouldResolvePlaceholderUsingCustomMapping()
throws SQLException {
assertThat(new PlatformPlaceholderDatabaseDriverResolver()
.withDriverPlatform(DatabaseDriver.POSTGRESQL, "postgres")
.resolveAll(dataSourceWithProductName("PostgreSQL"), "schema-@@platform@@.sql"))
.containsExactly("schema-postgres.sql");
}
@Test
void resolveAllWithDataSourceWhenValueIsAnEmptyStringShouldReturnValueUnchanged() {
assertThat(new PlatformPlaceholderDatabaseDriverResolver().resolveAll(mock(DataSource.class), ""))
.containsExactly("");
}
@Test
void resolveAllWithDataSourceWhenDriverIsUnknownShouldThrow() {
assertThatIllegalStateException().isThrownBy(() -> new PlatformPlaceholderDatabaseDriverResolver()
.resolveAll(dataSourceWithProductName("CustomDB"), "schema-@@platform@@.sql"));
}
@Test
void resolveAllWithDataSourceWhenPlaceholderIsCustomizedShouldResolvePlaceholders() throws SQLException {
assertThat(new PlatformPlaceholderDatabaseDriverResolver("##platform##")
.resolveAll(dataSourceWithProductName("PostgreSQL"), "schema-##platform##.sql", "schema-@@platform@@.sql"))
.containsExactly("schema-postgresql.sql", "schema-@@platform@@.sql");
}
private DataSource dataSourceWithProductName(String productName) throws SQLException {
DataSource dataSource = mock(DataSource.class);
Connection connection = mock(Connection.class);
given(dataSource.getConnection()).willReturn(connection);
DatabaseMetaData metadata = mock(DatabaseMetaData.class);
given(connection.getMetaData()).willReturn(metadata);
given(metadata.getDatabaseProductName()).willReturn(productName);
return dataSource;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Abstract base class for {@link DataSourcePoolMetadata} tests.
*
* @param <D> the data source pool metadata type
* @author Stephane Nicoll
* @author Artsiom Yudovin
*/
abstract class AbstractDataSourcePoolMetadataTests<D extends AbstractDataSourcePoolMetadata<?>> {
/**
* Return a data source metadata instance with a min size of 0 and max size of 2. Idle
* connections are not reclaimed immediately.
* @return the data source metadata
*/
protected abstract D getDataSourceMetadata();
@Test
void getMaxPoolSize() {
assertThat(getDataSourceMetadata().getMax()).isEqualTo(2);
}
@Test
void getMinPoolSize() {
assertThat(getDataSourceMetadata().getMin()).isZero();
}
@Test
void getPoolSizeNoConnection() {
// Make sure the pool is initialized
JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource());
jdbcTemplate.execute((ConnectionCallback<Void>) (connection) -> null);
assertThat(getDataSourceMetadata().getActive()).isZero();
assertThat(getDataSourceMetadata().getUsage()).isZero();
}
@Test
void getPoolSizeOneConnection() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource());
jdbcTemplate.execute((ConnectionCallback<Void>) (connection) -> {
assertThat(getDataSourceMetadata().getActive()).isOne();
assertThat(getDataSourceMetadata().getUsage()).isEqualTo(0.5f);
return null;
});
}
@Test
void getIdle() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource());
jdbcTemplate.execute((ConnectionCallback<Void>) (connection) -> null);
assertThat(getDataSourceMetadata().getIdle()).isOne();
}
@Test
void getPoolSizeTwoConnections() {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource());
jdbcTemplate.execute((ConnectionCallback<Void>) (connection) -> {
jdbcTemplate.execute((ConnectionCallback<Void>) (connection1) -> {
assertThat(getDataSourceMetadata().getActive()).isEqualTo(2);
assertThat(getDataSourceMetadata().getUsage()).isOne();
return null;
});
return null;
});
}
@Test
abstract void getValidationQuery() throws Exception;
@Test
abstract void getDefaultAutoCommit() throws Exception;
protected DataSourceBuilder<?> initializeBuilder() {
return DataSourceBuilder.create()
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:test")
.username("sa");
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import java.time.Duration;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CommonsDbcp2DataSourcePoolMetadata}.
*
* @author Stephane Nicoll
*/
class CommonsDbcp2DataSourcePoolMetadataTests
extends AbstractDataSourcePoolMetadataTests<CommonsDbcp2DataSourcePoolMetadata> {
private final CommonsDbcp2DataSourcePoolMetadata dataSourceMetadata = createDataSourceMetadata(0, 2);
@Override
protected CommonsDbcp2DataSourcePoolMetadata getDataSourceMetadata() {
return this.dataSourceMetadata;
}
@Test
void getPoolUsageWithNoCurrent() {
CommonsDbcp2DataSourcePoolMetadata dsm = new CommonsDbcp2DataSourcePoolMetadata(createDataSource()) {
@Override
public Integer getActive() {
return null;
}
};
assertThat(dsm.getUsage()).isNull();
}
@Test
void getPoolUsageWithNoMax() {
CommonsDbcp2DataSourcePoolMetadata dsm = new CommonsDbcp2DataSourcePoolMetadata(createDataSource()) {
@Override
public Integer getMax() {
return null;
}
};
assertThat(dsm.getUsage()).isNull();
}
@Test
void getPoolUsageWithUnlimitedPool() {
DataSourcePoolMetadata unlimitedDataSource = createDataSourceMetadata(0, -1);
assertThat(unlimitedDataSource.getUsage()).isEqualTo(-1f);
}
@Override
public void getValidationQuery() {
BasicDataSource dataSource = createDataSource();
dataSource.setValidationQuery("SELECT FROM FOO");
assertThat(new CommonsDbcp2DataSourcePoolMetadata(dataSource).getValidationQuery())
.isEqualTo("SELECT FROM FOO");
}
@Override
public void getDefaultAutoCommit() {
BasicDataSource dataSource = createDataSource();
dataSource.setDefaultAutoCommit(false);
assertThat(new CommonsDbcp2DataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse();
}
private CommonsDbcp2DataSourcePoolMetadata createDataSourceMetadata(int minSize, int maxSize) {
BasicDataSource dataSource = createDataSource();
dataSource.setMinIdle(minSize);
dataSource.setMaxTotal(maxSize);
dataSource.setMinEvictableIdle(Duration.ofSeconds(5));
return new CommonsDbcp2DataSourcePoolMetadata(dataSource);
}
private BasicDataSource createDataSource() {
return initializeBuilder().type(BasicDataSource.class).build();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import java.util.Arrays;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Tests for {@link CompositeDataSourcePoolMetadataProvider}.
*
* @author Stephane Nicoll
*/
@ExtendWith(MockitoExtension.class)
class CompositeDataSourcePoolMetadataProviderTests {
@Mock
private DataSourcePoolMetadataProvider firstProvider;
@Mock
private DataSourcePoolMetadata first;
@Mock
private DataSource firstDataSource;
@Mock
private DataSourcePoolMetadataProvider secondProvider;
@Mock
private DataSourcePoolMetadata second;
@Mock
private DataSource secondDataSource;
@Mock
private DataSource unknownDataSource;
@BeforeEach
void setup() {
given(this.firstProvider.getDataSourcePoolMetadata(this.firstDataSource)).willReturn(this.first);
given(this.firstProvider.getDataSourcePoolMetadata(this.secondDataSource)).willReturn(this.second);
}
@Test
void createWithProviders() {
CompositeDataSourcePoolMetadataProvider provider = new CompositeDataSourcePoolMetadataProvider(
Arrays.asList(this.firstProvider, this.secondProvider));
assertThat(provider.getDataSourcePoolMetadata(this.firstDataSource)).isSameAs(this.first);
assertThat(provider.getDataSourcePoolMetadata(this.secondDataSource)).isSameAs(this.second);
assertThat(provider.getDataSourcePoolMetadata(this.unknownDataSource)).isNull();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import com.zaxxer.hikari.HikariDataSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HikariDataSourcePoolMetadata}.
*
* @author Stephane Nicoll
*/
public class HikariDataSourcePoolMetadataTests
extends AbstractDataSourcePoolMetadataTests<HikariDataSourcePoolMetadata> {
private final HikariDataSourcePoolMetadata dataSourceMetadata = new HikariDataSourcePoolMetadata(
createDataSource(0, 2));
@Override
protected HikariDataSourcePoolMetadata getDataSourceMetadata() {
return this.dataSourceMetadata;
}
@Override
public void getValidationQuery() {
HikariDataSource dataSource = createDataSource(0, 4);
dataSource.setConnectionTestQuery("SELECT FROM FOO");
assertThat(new HikariDataSourcePoolMetadata(dataSource).getValidationQuery()).isEqualTo("SELECT FROM FOO");
}
@Override
public void getDefaultAutoCommit() {
HikariDataSource dataSource = createDataSource(0, 4);
dataSource.setAutoCommit(false);
assertThat(new HikariDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse();
}
private HikariDataSource createDataSource(int minSize, int maxSize) {
HikariDataSource dataSource = initializeBuilder().type(HikariDataSource.class).build();
dataSource.setMinimumIdle(minSize);
dataSource.setMaximumPoolSize(maxSize);
dataSource.setIdleTimeout(5000);
return dataSource;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OracleUcpDataSourcePoolMetadata}.
*
* @author Fabio Grassi
*/
class OracleUcpDataSourcePoolMetadataTests
extends AbstractDataSourcePoolMetadataTests<OracleUcpDataSourcePoolMetadata> {
private final OracleUcpDataSourcePoolMetadata dataSourceMetadata = new OracleUcpDataSourcePoolMetadata(
createDataSource(0, 2));
@Override
protected OracleUcpDataSourcePoolMetadata getDataSourceMetadata() {
return this.dataSourceMetadata;
}
@Override
void getValidationQuery() throws SQLException {
PoolDataSource dataSource = createDataSource(0, 4);
dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL");
assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getValidationQuery())
.isEqualTo("SELECT NULL FROM DUAL");
}
@Override
void getDefaultAutoCommit() throws SQLException {
PoolDataSource dataSource = createDataSource(0, 4);
dataSource.setConnectionProperty("autoCommit", "false");
assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse();
}
private PoolDataSource createDataSource(int minSize, int maxSize) {
try {
PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build();
dataSource.setInitialPoolSize(minSize);
dataSource.setMinPoolSize(minSize);
dataSource.setMaxPoolSize(maxSize);
return dataSource;
}
catch (SQLException ex) {
throw new IllegalStateException("Error while configuring PoolDataSource", ex);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.metadata;
import org.apache.tomcat.jdbc.pool.DataSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TomcatDataSourcePoolMetadata}.
*
* @author Stephane Nicoll
*/
public class TomcatDataSourcePoolMetadataTests
extends AbstractDataSourcePoolMetadataTests<TomcatDataSourcePoolMetadata> {
private final TomcatDataSourcePoolMetadata dataSourceMetadata = new TomcatDataSourcePoolMetadata(
createDataSource(0, 2));
@Override
protected TomcatDataSourcePoolMetadata getDataSourceMetadata() {
return this.dataSourceMetadata;
}
@Override
public void getValidationQuery() {
DataSource dataSource = createDataSource(0, 4);
dataSource.setValidationQuery("SELECT FROM FOO");
assertThat(new TomcatDataSourcePoolMetadata(dataSource).getValidationQuery()).isEqualTo("SELECT FROM FOO");
}
@Override
public void getDefaultAutoCommit() {
DataSource dataSource = createDataSource(0, 4);
dataSource.setDefaultAutoCommit(false);
assertThat(new TomcatDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse();
}
private DataSource createDataSource(int minSize, int maxSize) {
DataSource dataSource = initializeBuilder().type(DataSource.class).build();
dataSource.setMinIdle(minSize);
dataSource.setMaxActive(maxSize);
dataSource.setMinEvictableIdleTimeMillis(5000);
// Avoid warnings
dataSource.setInitialSize(minSize);
dataSource.setMaxIdle(maxSize);
return dataSource;
}
}