Skip to content

Commit

Permalink
Bind datasource settings to specific namespaces
Browse files Browse the repository at this point in the history
Previously, all datasource-related settings were bound to
`spring.datasource`, including datasource-specific settings.

Update `DataSourceBuilder` to bind to an arbitrary namespace and provide
a default one for known types (i.e. the tomcat implementation now binds
to `spring.datasource.tomcat`). Since `spring.datasource` is used for
common settings, it is still bound by default as well which makes this
change backward compatible.

Users were redirected to the use of `ConfigurationProperties` to
configure extra datasource instances. This is still valid but they now
can also use `DataSourceBuilder` directly to achieve the same effect and
still retain common settings if need to be.

Closes spring-projectsgh-2183
  • Loading branch information
snicoll committed May 19, 2015
1 parent e0dfe9f commit e71504b
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.DataSourceHealthIndicator;
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator;
Expand All @@ -47,12 +49,12 @@
import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;

import static org.junit.Assert.assertEquals;

Expand Down Expand Up @@ -406,10 +408,12 @@ public void notElasticSearchHealthIndicator() {
@EnableConfigurationProperties
protected static class DataSourceConfig {

@Autowired
private ConfigurableEnvironment environment;

@Bean
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public DataSource dataSource() {
return DataSourceBuilder.create()
return DataSourceBuilder.create().environment(this.environment)
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:test").username("sa").build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
Expand Down Expand Up @@ -111,11 +112,14 @@ protected static class NonEmbeddedConfiguration {
@Autowired
private DataSourceProperties properties;

@Autowired
private ConfigurableEnvironment environment;

@Bean
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public DataSource dataSource() {
DataSourceBuilder factory = DataSourceBuilder
.create(this.properties.getClassLoader())
.environment(this.environment)
.driverClassName(this.properties.getDriverClassName())
.url(this.properties.getUrl())
.username(this.properties.getUsername())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
Expand All @@ -17,39 +17,55 @@
package org.springframework.boot.autoconfigure.jdbc;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
* Convenience class for building a {@link DataSource} with common implementations and
* properties. If Tomcat, HikariCP or Commons DBCP are on the classpath one of them will
* be selected (in that order with Tomcat first). In the interest of a uniform interface,
* and so that there can be a fallback to an embedded database if one can be detected on
* the classpath, only a small set of common configuration properties are supported. To
* inject additional properties into the result you can downcast it, or use
* <code>@ConfigurationProperties</code>.
* the classpath, only a small set of common configuration properties are supported.
* <p>
* If the {@link ConfigurableEnvironment Environment} is set, additional specific properties
* can be injected. The prefix for the properties is auto-detected for known types but it
* can also be set explicitly via {@link #configurationPrefix(String)}. If that option does
* not work for you, you can also use <code>@ConfigurationProperties</code>.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.1.0
*/
public class DataSourceBuilder {

private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {
"org.apache.tomcat.jdbc.pool.DataSource",
"com.zaxxer.hikari.HikariDataSource",
"org.apache.commons.dbcp.BasicDataSource",
"org.apache.commons.dbcp2.BasicDataSource" };
private static final Map<String, String> DATA_SOURCE_TYPE_MAPPING = createDataSourceTypeMapping();

private static Map<String, String> createDataSourceTypeMapping() {
Map<String,String> mapping = new LinkedHashMap<String, String>();
mapping.put("org.apache.tomcat.jdbc.pool.DataSource", "spring.datasource.tomcat");
mapping.put("com.zaxxer.hikari.HikariDataSource", "spring.datasource.hikari");
mapping.put("org.apache.commons.dbcp.BasicDataSource", "spring.datasource.dbcp");
mapping.put("org.apache.commons.dbcp2.BasicDataSource", "spring.datasource.dbcp2");
return mapping;
}

private Class<? extends DataSource> type;

private ClassLoader classLoader;

private ConfigurableEnvironment environment;

private String configurationPrefix;

private Map<String, String> properties = new HashMap<String, String>();

public static DataSourceBuilder create() {
Expand Down Expand Up @@ -84,13 +100,53 @@ private void maybeGetDriverClassName() {
private void bind(DataSource result) {
MutablePropertyValues properties = new MutablePropertyValues(this.properties);
new RelaxedDataBinder(result).withAlias("url", "jdbcUrl").bind(properties);
if (this.environment != null) {
PropertySourcesPropertyValues pvs = new PropertySourcesPropertyValues(
this.environment.getPropertySources());
new RelaxedDataBinder(result, DataSourceProperties.PREFIX).bind(pvs);
String prefix = getConfigurationPrefix(result.getClass());
if (StringUtils.hasText(prefix) && !DataSourceProperties.PREFIX.equals(prefix)) {
new RelaxedDataBinder(result, prefix).bind(pvs);
}
}
}

private String getConfigurationPrefix(Class<? extends DataSource> datasourceType) {
if (StringUtils.hasText(this.configurationPrefix)) {
return this.configurationPrefix;
}
return DATA_SOURCE_TYPE_MAPPING.get(datasourceType.getName());
}

public DataSourceBuilder type(Class<? extends DataSource> type) {
this.type = type;
return this;
}

/**
* Specify the {@link ConfigurableEnvironment environment}. When set, configuration
* properties defined from the <code>spring.datasource</code> prefix are automatically
* bound. DataSource specific properties can also bound using if the
* {@link #configurationPrefix(String) configurationPrefix} is set or if a default was
* detected.
* @param environment the environment to set
* @return the builder instance
*/
public DataSourceBuilder environment(ConfigurableEnvironment environment) {
this.environment = environment;
return this;
}

/**
* Set the configuration prefix to use to inject additional configuration properties.
* @param prefix the datasource specific configuration prefix to use
* @return the builder instance
*/
public DataSourceBuilder configurationPrefix(String prefix) {
this.configurationPrefix = prefix;
return this;
}

public DataSourceBuilder url(String url) {
this.properties.put("url", url);
return this;
Expand All @@ -116,7 +172,7 @@ public Class<? extends DataSource> findType() {
if (this.type != null) {
return this.type;
}
for (String name : DATA_SOURCE_TYPE_NAMES) {
for (String name : DATA_SOURCE_TYPE_MAPPING.keySet()) {
try {
return (Class<? extends DataSource>) ClassUtils.forName(name,
this.classLoader);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
Expand Down Expand Up @@ -31,21 +31,28 @@
*/
class DataSourceConfigMetadata {

@ConfigurationProperties(DataSourceProperties.PREFIX)
@ConfigurationProperties("spring.datasource.tomcat")
public DataSource tomcatDataSource() {
return (DataSource) DataSourceBuilder.create().type(DataSource.class).build();
}

@ConfigurationProperties(DataSourceProperties.PREFIX)
@ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource hikariDataSource() {
return (HikariDataSource) DataSourceBuilder.create().type(HikariDataSource.class)
.build();
}

@ConfigurationProperties(DataSourceProperties.PREFIX)
@ConfigurationProperties("spring.datasource.dbcp")
public BasicDataSource dbcpDataSource() {
return (BasicDataSource) DataSourceBuilder.create().type(BasicDataSource.class)
.build();
}

@ConfigurationProperties("spring.datasource.dbcp2")
public org.apache.commons.dbcp2.BasicDataSource dbcp2DataSource() {
return (org.apache.commons.dbcp2.BasicDataSource) DataSourceBuilder.create()
.type(org.apache.commons.dbcp2.BasicDataSource.class)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
Expand All @@ -21,12 +21,14 @@
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.junit.Test;
import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand All @@ -40,6 +42,7 @@
public class CommonsDataSourceConfigurationTests {

private static final String PREFIX = "spring.datasource.";
private static final String PREFIX_IMPL = PREFIX + "dbcp.";

private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

Expand All @@ -56,14 +59,14 @@ public void testDataSourcePropertiesOverridden() throws Exception {
this.context.register(CommonsDataSourceConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, PREFIX
+ "url:jdbc:foo//bar/spam");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "testWhileIdle:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "testOnBorrow:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "testOnReturn:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL + "testWhileIdle:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL + "testOnBorrow:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL + "testOnReturn:true");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL
+ "timeBetweenEvictionRunsMillis:10000");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL
+ "minEvictableIdleTimeMillis:12345");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "maxWait:1234");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX_IMPL + "maxWait:1234");
this.context.refresh();
BasicDataSource ds = this.context.getBean(BasicDataSource.class);
assertEquals("jdbc:foo//bar/spam", ds.getUrl());
Expand Down Expand Up @@ -91,10 +94,13 @@ public void testDataSourceDefaultsPreserved() throws Exception {
@EnableConfigurationProperties
protected static class CommonsDataSourceConfiguration {

@Autowired
private ConfigurableEnvironment environment;

@Bean
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public DataSource dataSource() {
return DataSourceBuilder.create().type(BasicDataSource.class).build();
return DataSourceBuilder.create().environment(this.environment)
.type(BasicDataSource.class).build();
}

}
Expand Down

0 comments on commit e71504b

Please sign in to comment.