Create spring-boot-jdbc module
This commit is contained in:
committed by
Phillip Webb
parent
5360ef8321
commit
8a1a5160c3
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.boot.jdbc.DataSourceBuilderRuntimeHints
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user