Commit 50190a4d authored by Dave Syer's avatar Dave Syer

Add support for HikariDataSource

We still prefer Tomcat if it is available (that can change
if the community asks loudly enough). Hikari is supported
via the same spring.datasource.* properties as Tomcat (and
DBCP), with some modifications:

* The validation and timeout settings are not as fine-grained
in Hikari, so many of them will simply be ignored. The most
common options (url, username, password, driverClassName) all
work as expected.

* The Hikari team recommends using a vendor-specific DataSource
via spring.datasource.dataSourceClassName and supplying it with
Properties (spring.datasource.hikari.*).

Hikari prefers the JDBC4 isValid() API (encapsulates vendor-
specific queries) which is probably a good thing, but we
haven't provided any explicit support or testing for that yet.

Fixes gh-418
parent f46d281b
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -61,6 +62,11 @@ ...@@ -61,6 +62,11 @@
<artifactId>tomcat-jdbc</artifactId> <artifactId>tomcat-jdbc</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
......
...@@ -156,6 +156,13 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -156,6 +156,13 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
} }
@Conditional(DataSourceAutoConfiguration.HikariDatabaseCondition.class)
@ConditionalOnMissingBean(DataSource.class)
@Import(HikariDataSourceConfiguration.class)
protected static class HikariConfiguration {
}
@Conditional(DataSourceAutoConfiguration.BasicDatabaseCondition.class) @Conditional(DataSourceAutoConfiguration.BasicDatabaseCondition.class)
@ConditionalOnMissingBean(DataSource.class) @ConditionalOnMissingBean(DataSource.class)
@Import(CommonsDataSourceConfiguration.class) @Import(CommonsDataSourceConfiguration.class)
...@@ -260,6 +267,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -260,6 +267,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
*/ */
static class BasicDatabaseCondition extends NonEmbeddedDatabaseCondition { static class BasicDatabaseCondition extends NonEmbeddedDatabaseCondition {
private final Condition hikariCondition = new HikariDatabaseCondition();
private final Condition tomcatCondition = new TomcatDatabaseCondition(); private final Condition tomcatCondition = new TomcatDatabaseCondition();
@Override @Override
...@@ -270,7 +279,30 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -270,7 +279,30 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
if (matches(context, metadata, this.tomcatCondition)) { if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition)) {
return ConditionOutcome.noMatch("other DataSource");
}
return super.getMatchOutcome(context, metadata);
}
}
/**
* {@link Condition} to detect when a Hikari DataSource backed database is used.
*/
static class HikariDatabaseCondition extends NonEmbeddedDatabaseCondition {
private final Condition tomcatCondition = new TomcatDatabaseCondition();
@Override
protected String getDataSourceClassName() {
return "com.zaxxer.hikari.HikariDataSource";
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
if (anyMatches(context, metadata, this.tomcatCondition)) {
return ConditionOutcome.noMatch("Tomcat DataSource"); return ConditionOutcome.noMatch("Tomcat DataSource");
} }
return super.getMatchOutcome(context, metadata); return super.getMatchOutcome(context, metadata);
...@@ -295,6 +327,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -295,6 +327,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
*/ */
static class EmbeddedDatabaseCondition extends SpringBootCondition { static class EmbeddedDatabaseCondition extends SpringBootCondition {
private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();
private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition(); private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();
private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition(); private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();
...@@ -302,7 +336,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -302,7 +336,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
if (anyMatches(context, metadata, this.tomcatCondition, this.dbcpCondition)) { if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition,
this.dbcpCondition)) {
return ConditionOutcome return ConditionOutcome
.noMatch("existing non-embedded database detected"); .noMatch("existing non-embedded database detected");
} }
...@@ -321,6 +356,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -321,6 +356,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
*/ */
static class DatabaseCondition extends SpringBootCondition { static class DatabaseCondition extends SpringBootCondition {
private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();
private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition(); private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();
private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition(); private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();
...@@ -331,8 +368,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { ...@@ -331,8 +368,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
if (anyMatches(context, metadata, this.tomcatCondition, this.dbcpCondition, if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition,
this.embeddedCondition)) { this.dbcpCondition, this.embeddedCondition)) {
return ConditionOutcome.match("existing auto database detected"); return ConditionOutcome.match("existing auto database detected");
} }
......
/*
* Copyright 2012-2014 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
*
* http://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.autoconfigure.jdbc;
import java.util.Properties;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import com.zaxxer.hikari.HikariDataSource;
/**
* Configuration for a HikariCP database pool. The HikariCP pool is a popular data source
* implementation that provides high performance as well as some useful opinionated
* defaults. For compatibility with other DataSource implementations accepts configuration
* via properties in "spring.datasource.*", e.g. "url", "driverClassName", "username",
* "password" (and some others but the full list supported by the Tomcat pool is not
* applicable). Note that the Hikari team recommends using a "dataSourceClassName" and a
* Properties instance (specified here as "spring.datasource.hikari.*"). This makes the
* binding potentially vendor specific, but gives you full control of all the native
* features in the vendor's DataSource.
*
* @author Dave Syer
* @see DataSourceAutoConfiguration
*/
@Configuration
public class HikariDataSourceConfiguration extends AbstractDataSourceConfiguration {
private String dataSourceClassName;
private String username;
private HikariDataSource pool;
private Properties hikari = new Properties();
@Bean(destroyMethod = "shutdown")
public DataSource dataSource() {
this.pool = new HikariDataSource();
if (this.dataSourceClassName == null) {
this.pool.setDriverClassName(getDriverClassName());
}
else {
this.pool.setDataSourceClassName(this.dataSourceClassName);
this.pool.setDataSourceProperties(this.hikari);
}
this.pool.setJdbcUrl(getUrl());
if (getUsername() != null) {
this.pool.setUsername(getUsername());
}
if (getPassword() != null) {
this.pool.setPassword(getPassword());
}
this.pool.setMaximumPoolSize(getMaxActive());
this.pool.setMinimumIdle(getMinIdle());
if (isTestOnBorrow()) {
this.pool.setConnectionInitSql(getValidationQuery());
}
else {
this.pool.setConnectionTestQuery(getValidationQuery());
}
if (getMaxWaitMillis() != null) {
this.pool.setMaxLifetime(getMaxWaitMillis());
}
return this.pool;
}
@PreDestroy
public void close() {
if (this.pool != null) {
this.pool.close();
}
}
/**
* @param dataSourceClassName the dataSourceClassName to set
*/
public void setDataSourceClassName(String dataSourceClassName) {
this.dataSourceClassName = dataSourceClassName;
}
@Override
public void setUsername(String username) {
this.username = username;
}
/**
* @return the hikari data source properties
*/
public Properties getHikari() {
return this.hikari;
}
@Override
protected String getUsername() {
if (StringUtils.hasText(this.username)) {
return this.username;
}
if (this.dataSourceClassName == null
&& EmbeddedDatabaseConnection.isEmbedded(getDriverClassName())) {
return "sa";
}
return null;
}
}
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection; import java.sql.Connection;
import java.sql.Driver; import java.sql.Driver;
import java.sql.DriverPropertyInfo; import java.sql.DriverPropertyInfo;
...@@ -44,6 +46,8 @@ import org.springframework.jdbc.core.JdbcTemplate; ...@@ -44,6 +46,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import com.zaxxer.hikari.HikariDataSource;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
...@@ -73,6 +77,30 @@ public class DataSourceAutoConfigurationTests { ...@@ -73,6 +77,30 @@ public class DataSourceAutoConfigurationTests {
assertNotNull(this.context.getBean(DataSource.class)); assertNotNull(this.context.getBean(DataSource.class));
} }
@Test
public void testTomcatIsFallback() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb");
this.context.setClassLoader(new URLClassLoader(new URL[0], getClass()
.getClassLoader()) {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (name.startsWith("org.apache.tomcat")) {
throw new ClassNotFoundException();
}
return super.loadClass(name, resolve);
}
});
this.context.register(DataSourceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
DataSource bean = this.context.getBean(DataSource.class);
HikariDataSource pool = (HikariDataSource) bean;
assertEquals("jdbc:hsqldb:mem:testdb", pool.getJdbcUrl());
}
@Test @Test
public void testEmbeddedTypeDefaultsUsername() throws Exception { public void testEmbeddedTypeDefaultsUsername() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context, EnvironmentTestUtils.addEnvironment(this.context,
......
/*
* Copyright 2012-2014 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
*
* http://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.autoconfigure.jdbc;
import java.lang.reflect.Field;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.util.ReflectionUtils;
import com.zaxxer.hikari.HikariDataSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Tests for {@link HikariDataSourceConfiguration}.
*
* @author Dave Syer
*/
public class HikariDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.";
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@After
public void restore() {
EmbeddedDatabaseConnection.override = null;
}
@Test
public void testDataSourceExists() throws Exception {
this.context.register(HikariDataSourceConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(DataSource.class));
assertNotNull(this.context.getBean(HikariDataSource.class));
}
@Test
public void testDataSourcePropertiesOverridden() throws Exception {
this.context.register(HikariDataSourceConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, PREFIX
+ "url:jdbc:foo//bar/spam");
EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "maxWait:1234");
this.context.refresh();
HikariDataSource ds = this.context.getBean(HikariDataSource.class);
assertEquals("jdbc:foo//bar/spam", ds.getJdbcUrl());
assertEquals(1234, ds.getMaxLifetime());
// TODO: test JDBC4 isValid()
}
@Test
public void testDataSourceGenericPropertiesOverridden() throws Exception {
this.context.register(HikariDataSourceConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, PREFIX
+ "hikari.databaseName:foo", PREFIX
+ "dataSourceClassName:org.h2.JDBCDataSource");
this.context.refresh();
HikariDataSource ds = this.context.getBean(HikariDataSource.class);
assertEquals("foo", ds.getDataSourceProperties().getProperty("databaseName"));
}
@Test
public void testDataSourceDefaultsPreserved() throws Exception {
this.context.register(HikariDataSourceConfiguration.class);
this.context.refresh();
HikariDataSource ds = this.context.getBean(HikariDataSource.class);
assertEquals(1800000, ds.getMaxLifetime());
}
@Test(expected = BeanCreationException.class)
public void testBadUrl() throws Exception {
EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE;
this.context.register(HikariDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(DataSource.class));
}
@Test(expected = BeanCreationException.class)
public void testBadDriverClass() throws Exception {
EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE;
this.context.register(HikariDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(DataSource.class));
}
@SuppressWarnings("unchecked")
public static <T> T getField(Class<?> target, String name) {
Field field = ReflectionUtils.findField(target, name, null);
ReflectionUtils.makeAccessible(field);
return (T) ReflectionUtils.getField(field, target);
}
}
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
<hibernate-entitymanager.version>${hibernate.version}</hibernate-entitymanager.version> <hibernate-entitymanager.version>${hibernate.version}</hibernate-entitymanager.version>
<hibernate-jpa-api.version>1.0.1.Final</hibernate-jpa-api.version> <hibernate-jpa-api.version>1.0.1.Final</hibernate-jpa-api.version>
<hibernate-validator.version>5.0.3.Final</hibernate-validator.version> <hibernate-validator.version>5.0.3.Final</hibernate-validator.version>
<hikaricp.version>1.3.5</hikaricp.version>
<httpclient.version>4.3.3</httpclient.version> <httpclient.version>4.3.3</httpclient.version>
<httpasyncclient.version>4.0.1</httpasyncclient.version> <httpasyncclient.version>4.0.1</httpasyncclient.version>
<hsqldb.version>2.3.2</hsqldb.version> <hsqldb.version>2.3.2</hsqldb.version>
...@@ -145,6 +146,11 @@ ...@@ -145,6 +146,11 @@
<artifactId>jackson-datatype-joda</artifactId> <artifactId>jackson-datatype-joda</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<dependency> <dependency>
<groupId>commons-dbcp</groupId> <groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId> <artifactId>commons-dbcp</artifactId>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment