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
This commit is contained in:
parent
f46d281b22
commit
50190a4de7
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
@ConditionalOnMissingBean(DataSource.class)
|
||||
@Import(CommonsDataSourceConfiguration.class)
|
||||
|
|
@ -260,6 +267,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
*/
|
||||
static class BasicDatabaseCondition extends NonEmbeddedDatabaseCondition {
|
||||
|
||||
private final Condition hikariCondition = new HikariDatabaseCondition();
|
||||
|
||||
private final Condition tomcatCondition = new TomcatDatabaseCondition();
|
||||
|
||||
@Override
|
||||
|
|
@ -270,7 +279,30 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
@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);
|
||||
|
|
@ -295,6 +327,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
*/
|
||||
static class EmbeddedDatabaseCondition extends SpringBootCondition {
|
||||
|
||||
private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();
|
||||
|
||||
private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();
|
||||
|
||||
private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();
|
||||
|
|
@ -302,7 +336,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
@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");
|
||||
}
|
||||
|
|
@ -321,6 +356,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
*/
|
||||
static class DatabaseCondition extends SpringBootCondition {
|
||||
|
||||
private final SpringBootCondition hikariCondition = new HikariDatabaseCondition();
|
||||
|
||||
private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition();
|
||||
|
||||
private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition();
|
||||
|
|
@ -331,8 +368,8 @@ public class DataSourceAutoConfiguration implements EnvironmentAware {
|
|||
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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -44,6 +46,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||
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;
|
||||
|
|
@ -73,6 +77,30 @@ public class DataSourceAutoConfigurationTests {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 @@
|
|||
<hibernate-entitymanager.version>${hibernate.version}</hibernate-entitymanager.version>
|
||||
<hibernate-jpa-api.version>1.0.1.Final</hibernate-jpa-api.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>
|
||||
<httpasyncclient.version>4.0.1</httpasyncclient.version>
|
||||
<hsqldb.version>2.3.2</hsqldb.version>
|
||||
|
|
@ -145,6 +146,11 @@
|
|||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>${hikaricp.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-dbcp</groupId>
|
||||
<artifactId>commons-dbcp</artifactId>
|
||||
|
|
|
|||
Loading…
Reference in New Issue