Skip to content

Commit

Permalink
Add support for HikariDataSource
Browse files Browse the repository at this point in the history
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 spring-projectsgh-418
  • Loading branch information
Dave Syer authored and mdeinum committed Jun 6, 2014
1 parent f6ca4b7 commit 3df1ae8
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 5 deletions.
8 changes: 7 additions & 1 deletion spring-boot-autoconfigure/pom.xml
@@ -1,5 +1,6 @@
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -61,6 +62,11 @@
<artifactId>tomcat-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
Expand Down
Expand Up @@ -156,6 +156,13 @@ protected static class TomcatConfiguration {

}

@Conditional(DataSourceAutoConfiguration.HikariDatabaseCondition.class)
@ConditionalOnMissingBean(DataSource.class)
@Import(HikariDataSourceConfiguration.class)
protected static class HikariConfiguration {

}

@Conditional(DataSourceAutoConfiguration.BasicDatabaseCondition.class)
@ConditionalOnMissingBean(DataSource.class)
@Import(CommonsDataSourceConfiguration.class)
Expand Down Expand Up @@ -260,6 +267,8 @@ private String getUrl(Environment environment, ClassLoader classLoader) {
*/
static class BasicDatabaseCondition extends NonEmbeddedDatabaseCondition {

private final Condition hikariCondition = new HikariDatabaseCondition();

private final Condition tomcatCondition = new TomcatDatabaseCondition();

@Override
Expand All @@ -270,7 +279,30 @@ protected String getDataSourceClassName() {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
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 super.getMatchOutcome(context, metadata);
Expand All @@ -295,14 +327,17 @@ protected String getDataSourceClassName() {
*/
static class EmbeddedDatabaseCondition extends SpringBootCondition {

private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();

private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();

private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
if (anyMatches(context, metadata, this.tomcatCondition, this.dbcpCondition)) {
if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition,
this.dbcpCondition)) {
return ConditionOutcome
.noMatch("existing non-embedded database detected");
}
Expand All @@ -321,6 +356,8 @@ public ConditionOutcome getMatchOutcome(ConditionContext context,
*/
static class DatabaseCondition extends SpringBootCondition {

private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();

private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();

private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();
Expand All @@ -331,8 +368,8 @@ static class DatabaseCondition extends SpringBootCondition {
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {

if (anyMatches(context, metadata, this.tomcatCondition, this.dbcpCondition,
this.embeddedCondition)) {
if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition,
this.dbcpCondition, this.embeddedCondition)) {
return ConditionOutcome.match("existing auto database detected");
}

Expand Down
@@ -0,0 +1,122 @@
/*
* 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;
}

}
Expand Up @@ -16,6 +16,8 @@

package org.springframework.boot.autoconfigure.jdbc;

import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
Expand Down Expand Up @@ -44,6 +46,8 @@
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.ClassUtils;

import com.zaxxer.hikari.HikariDataSource;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
Expand Down Expand Up @@ -73,6 +77,30 @@ public void testDefaultDataSourceExists() throws Exception {
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
public void testEmbeddedTypeDefaultsUsername() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
Expand Down

0 comments on commit 3df1ae8

Please sign in to comment.