All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.boot.jdbc.DataSourceBuilder Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * 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.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.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
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.
 * 

* 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: *

    *
  • Hikari ({@code com.zaxxer.hikari.HikariDataSource})
  • *
  • Tomcat JDBC Pool ({@code org.apache.tomcat.jdbc.pool.DataSource})
  • *
  • Apache DBCP2 ({@code org.apache.commons.dbcp2.BasicDataSource})
  • *
  • Oracle UCP ({@code oracle.ucp.jdbc.PoolDataSourceImpl})
  • *
*

* The following non-pooling {@link DataSource} implementations can be used when * explicitly set as a {@link #type(Class) type}: *

    *
  • Spring's {@code SimpleDriverDataSource} * ({@code org.springframework.jdbc.datasource.SimpleDriverDataSource})
  • *
  • Oracle ({@code oracle.jdbc.datasource.OracleDataSource})
  • *
  • H2 ({@code org.h2.jdbcx.JdbcDataSource})
  • *
  • Postgres ({@code org.postgresql.ds.PGSimpleDataSource})
  • *
  • Any {@code DataSource} implementation with appropriately named methods
  • *
*

* This class is commonly used in an {@code @Bean} method and often combined with * {@code @ConfigurationProperties}. * * @param 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 { private final ClassLoader classLoader; private final Map values = new HashMap<>(); private Class type; private final DataSource deriveFrom; private DataSourceBuilder(ClassLoader classLoader) { this.classLoader = classLoader; this.deriveFrom = null; } @SuppressWarnings("unchecked") private DataSourceBuilder(T deriveFrom) { Assert.notNull(deriveFrom, "DataSource must not be null"); this.classLoader = deriveFrom.getClass().getClassLoader(); this.type = (Class) deriveFrom.getClass(); this.deriveFrom = deriveFrom; } /** * Set the {@link DataSource} type that should be built. * @param the datasource type * @param type the datasource type * @return this builder */ @SuppressWarnings("unchecked") public DataSourceBuilder type(Class type) { this.type = (Class) type; return (DataSourceBuilder) this; } /** * Set the URL that should be used when building the datasource. * @param url the JDBC url * @return this builder */ public DataSourceBuilder 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 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 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 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 properties = DataSourceProperties.forType(this.classLoader, this.type); DataSourceProperties deriveFromProperties = getDeriveFromProperties(); Class instanceType = (this.type != null) ? this.type : properties.getDataSourceInstanceType(); T dataSource = BeanUtils.instantiateClass(instanceType); Set 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) && this.values.containsKey(DataSourceProperty.URL)) { String url = this.values.get(DataSourceProperty.URL); DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url); properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName()); } return dataSource; } @SuppressWarnings("unchecked") private DataSourceProperties getDeriveFromProperties() { if (this.deriveFrom == null) { return null; } return DataSourceProperties.forType(this.classLoader, (Class) 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 * @since 2.5.0 */ public static DataSourceBuilder derivedFrom(DataSource dataSource) { if (dataSource instanceof EmbeddedDatabase) { try { dataSource = dataSource.unwrap(DataSource.class); } catch (SQLException ex) { throw new IllegalStateException("Unable to unwrap embedded database", ex); } } 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 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 { Class getDataSourceInstanceType(); boolean canSet(DataSourceProperty property); void set(T dataSource, DataSourceProperty property, String value); String get(T dataSource, DataSourceProperty property); static DataSourceProperties forType(ClassLoader classLoader, Class type) { MappedDataSourceProperties mapped = MappedDataSourceProperties.forType(classLoader, type); return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type); } } private static class MappedDataSourceProperties implements DataSourceProperties { private final Map> mappedProperties = new HashMap<>(); private final Class dataSourceType; @SuppressWarnings("unchecked") MappedDataSourceProperties() { this.dataSourceType = (Class) ResolvableType.forClass(MappedDataSourceProperties.class, getClass()) .resolveGeneric(); } @Override public Class getDataSourceInstanceType() { return this.dataSourceType; } protected void add(DataSourceProperty property, Getter getter, Setter setter) { add(property, String.class, getter, setter); } protected void add(DataSourceProperty property, Class type, Getter getter, Setter 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 mappedProperty = getMapping(property); if (mappedProperty != null) { mappedProperty.set(dataSource, value); } } @Override public String get(T dataSource, DataSourceProperty property) { MappedDataSourceProperty mappedProperty = getMapping(property); if (mappedProperty != null) { return mappedProperty.get(dataSource); } return null; } private MappedDataSourceProperty getMapping(DataSourceProperty property) { MappedDataSourceProperty mappedProperty = this.mappedProperties.get(property); UnsupportedDataSourcePropertyException.throwIf(!property.isOptional() && mappedProperty == null, () -> "No mapping found for " + property); return mappedProperty; } static MappedDataSourceProperties forType(ClassLoader classLoader, Class type) { MappedDataSourceProperties pooled = lookupPooled(classLoader, type); if (type == null || pooled != null) { return pooled; } return lookupBasic(classLoader, type); } private static MappedDataSourceProperties lookupPooled(ClassLoader classLoader, Class type) { MappedDataSourceProperties 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); return result; } private static MappedDataSourceProperties lookupBasic(ClassLoader classLoader, Class dataSourceType) { MappedDataSourceProperties 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 MappedDataSourceProperties lookup(ClassLoader classLoader, Class dataSourceType, MappedDataSourceProperties existing, String dataSourceClassName, Supplier> propertyMappingsSupplier, String... requiredClassNames) { if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) { return existing; } MappedDataSourceProperties propertyMappings = propertyMappingsSupplier.get(); return (dataSourceType == null || propertyMappings.getDataSourceInstanceType().isAssignableFrom(dataSourceType)) ? (MappedDataSourceProperties) 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 { private final DataSourceProperty property; private final Class type; private final Getter getter; private final Setter setter; MappedDataSourceProperty(DataSourceProperty property, Class type, Getter getter, Setter 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 implements DataSourceProperties { private final Map getters; private final Map setters; private final Class dataSourceType; ReflectionDataSourceProperties(Class dataSourceType) { Assert.state(dataSourceType != null, "No supported DataSource type found"); Map getters = new HashMap<>(); Map 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 map, DataSourceProperty property, Method method) { if (method != null) { map.put(property, method); } } @Override public Class 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 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 { V get(T instance) throws SQLException; } @FunctionalInterface private interface Setter { void set(T instance, V value) throws SQLException; } /** * {@link DataSourceProperties} for Hikari. */ private static class HikariDataSourceProperties extends MappedDataSourceProperties { 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 { 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 { 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, BasicDataSource::getPassword, BasicDataSource::setPassword); } } /** * {@link DataSourceProperties} for Oracle Pool. */ private static class OraclePoolDataSourceProperties extends MappedDataSourceProperties { @Override public Class 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 { 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); } } } /** * {@link DataSourceProperties} for Spring's {@link SimpleDriverDataSource}. */ private static class SimpleDataSourceProperties extends MappedDataSourceProperties { @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 { 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 { 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 { PostgresDataSourceProperties() { add(DataSourceProperty.URL, PGSimpleDataSource::getUrl, PGSimpleDataSource::setUrl); add(DataSourceProperty.USERNAME, PGSimpleDataSource::getUser, PGSimpleDataSource::setUser); add(DataSourceProperty.PASSWORD, PGSimpleDataSource::getPassword, PGSimpleDataSource::setPassword); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy