HikariPool-1 - dataSource or dataSourceClassName or jdbcUrl is required.解决方案

故障错误

最近在使用Spring Boot 2.x with H2 Database 以及JPA整合一个项目的时候出现了下面这一个故障:

ERROR 21448 --- [ main] com.zaxxer.hikari.HikariConfig : HikariPool-1 - dataSource or dataSourceClassName or jdbcUrl is required.
....
Caused by: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
	at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:955) ~[HikariCP-3.2.0.jar:na]
	at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:109) ~[HikariCP-3.2.0.jar:na]
	at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:180) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.getIsolatedConnection(DdlTransactionIsolatorNonJtaImpl.java:43) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.jdbcStatement(GenerationTargetToDatabase.java:77) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:53) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.applySqlString(SchemaDropperImpl.java:375) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.applySqlStrings(SchemaDropperImpl.java:359) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.dropFromMetadata(SchemaDropperImpl.java:241) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.performDrop(SchemaDropperImpl.java:154) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:126) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.internal.SchemaDropperImpl.doDrop(SchemaDropperImpl.java:112) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:144) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:72) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:310) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:467) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:939) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
	at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	... 20 common frames omitted

故障分析

废话不多说,我们来一起分析下故障。

根据上面的错误日志关键信息可以得知:

HikariPool-1 - dataSource or dataSourceClassName or jdbcUrl is required.

这里提示 dataSource or dataSourceClassName or jdbcUrl 是必须配置的,但是我反复检查了好几遍,我的配置文件中是已经配置了的,而且我发誓没有使用多个数据源,就是之前引入过Druid 依赖,后来又删掉了。

# 配置 Spring Data JPA
# 配置使用数据库类型
spring.jpa.database=h2
# 创建表的方式
# 方式一:通过表注解映射方式
# 自动建表规则
# create:Create the schema and destroy previous data
# create-drop:Create and then destroy the schema at the end of the session.
# update:Update the schema if necessary.
# none:Disable DDL handling
spring.jpa.hibernate.ddl-auto=create
# 自动建表和列映射规则
# 第一种规则:org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl:会把nickName映射为nickName
# 第二种规则:org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy:会把nickName映射为nick_name
# 第三种自定义规则:com.xingyun.customize.UpperTableColumnStrategy:会把nickName映射为NIKE_NAME
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# 方式二:通过脚本初始化建立数据库表
# 初始化数据库表
#spring.datasource.schema=classpath*:/script/test-schema.sql
# 初始化数据库数据
#spring.datasource.data=classpath*:/script/test_data.sql
# 是否显示SQL
spring.jpa.show-sql=true
# 是否显示Web 控制台
spring.jpa.open-in-view=true

# 配置Datasource
# 配置存储数据到内存
spring.datasource.url=jdbc:h2:mem:test_h2_db
##配置存储数据到文件
#spring.datasource.url=jdbc:h2:file:~/test_h2_db
### 配置数据库连接账号
spring.datasource.username=sa
### 配置数据库连接密码
spring.datasource.password=sa
### 配置使用数据库驱动
spring.datasource.driver-class-name=org.h2.Driver
### 配置数据源初始化类型 embedded|always|never
### 注意:spring.datasource.initialize=true已经过时,使用spring.datasource.initialization-mode替代
spring.datasource.initialization-mode=embedded
## 配置数据源类型
spring.datasource.type=com.zaxxer.hikari.HikariDataSource


# 配置H2 Database
# H2 web管理控制台需要devtools,如果没有添加该依赖仍然想要使用web 控制台,那么需要配置如下属性为true
spring.h2.console.enabled=true
# 配置H2 web 管理控制台的上下文
spring.h2.console.path=/h2-console
#进行该配置后,h2 web console就可以在远程访问了。否则只能在本机访问。
spring.h2.console.settings.web-allow-others=false

关于这个故障,网上好多都是说

  • spring.datasource.url 修改为spring.datasource.jdbc-url
  • spring.datasource.driverClassName修改为spring.datasource.driver-class-name

但是实际测试发现,效果貌似并不奏效。

那就自己找吧,我们根据上面的错误提示可以知道,错误发生在HikariDataSource 类和HikariConfig 类中。

HikariDataSource.java

public class HikariDataSource extends HikariConfig implements DataSource{
       ... 
      public HikariDataSource(HikariConfig configuration) {
              configuration.validate();
              ... 
      } 
}

HikaruiDatasource.java中有一个构造方法,构造方法中调用了validate方法,而错误就发生在这个验证方法中。

HikariConfig.java

public void validate() {
        if (this.poolName == null) {
            this.poolName = this.generatePoolName();
        } else if (this.isRegisterMbeans && this.poolName.contains(":")) {
            throw new IllegalArgumentException("poolName cannot contain ':' when used with JMX");
        }

        this.catalog = UtilityElf.getNullIfEmpty(this.catalog);
        this.connectionInitSql = UtilityElf.getNullIfEmpty(this.connectionInitSql);
        this.connectionTestQuery = UtilityElf.getNullIfEmpty(this.connectionTestQuery);
        this.transactionIsolationName = UtilityElf.getNullIfEmpty(this.transactionIsolationName);
        this.dataSourceClassName = UtilityElf.getNullIfEmpty(this.dataSourceClassName);
        this.dataSourceJndiName = UtilityElf.getNullIfEmpty(this.dataSourceJndiName);
        this.driverClassName = UtilityElf.getNullIfEmpty(this.driverClassName);
        this.jdbcUrl = UtilityElf.getNullIfEmpty(this.jdbcUrl);
        if (this.dataSource != null) {
            if (this.dataSourceClassName != null) {
                LOGGER.warn("{} - using dataSource and ignoring dataSourceClassName.", this.poolName);
            }
        } else if (this.dataSourceClassName != null) {
            if (this.driverClassName != null) {
                LOGGER.error("{} - cannot use driverClassName and dataSourceClassName together.", this.poolName);
                throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together.");
            }

            if (this.jdbcUrl != null) {
                LOGGER.warn("{} - using dataSourceClassName and ignoring jdbcUrl.", this.poolName);
            }
        } else if (this.jdbcUrl == null && this.dataSourceJndiName == null) {
            if (this.driverClassName != null) {
                LOGGER.error("{} - jdbcUrl is required with driverClassName.", this.poolName);
                throw new IllegalArgumentException("jdbcUrl is required with driverClassName.");
            }

            LOGGER.error("{} - dataSource or dataSourceClassName or jdbcUrl is required.", this.poolName);
            throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required.");
        }

        this.validateNumerics();
        if (LOGGER.isDebugEnabled() || unitTest) {
            this.logConfiguration();
        }

    }

根据代码来看,当 jdbcUrl 或者dataSourceJndiName 变量为空,那么就会抛出这个错误。

于是我尝试在代码里添加

spring.datasource.hikari.jdbc-url=jdbc:h2:mem:test_h2_db

但是还是不得行。。。

因此我怀疑是Spring Boot 的自动配置不知道什么原因失效了。

Spring Boot 项目拥有智能的自动配置功能,当检测到有H2 相关数据库连接的jar 包就会进行自动配置。

所谓的自动配置根据我的理解至少需要有两个操作:

  • 读取application.properties 配置文件中属性
  • 然后设置到实例对象中

然后通过查资料,在org.springframework.boot.jdbc 包下找到了DataSourceBuilder.java 这个类。

这个类很关键,令我茅塞顿开,明白了spring.datasource.urlspring.datasource.jdbc-url 之间的关系。

核心如下:

 private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {"com.zaxxer.hikari.HikariDataSource",
                                         "org.apache.tomcat.jdbc.pool.DataSource",
                                       "org.apache.commons.dbcp2.BasicDataSource"};
aliases.addAliases("url", new String[]{"jdbc-url"});
aliases.addAliases("username", new String[]{"user"});
  • 上面可以看到,这个类默认数组中有三种数据源连接池,HikariDataSource 就是其中一种。
  • 其次,url设置了别名映射,因此spring.datasource.url 就等价于 spring.datasource.jdbc-url

完整代码如下:

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.util.ClassUtils;

public final class DataSourceBuilder<T extends DataSource> {
    private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{"com.zaxxer.hikari.HikariDataSource", "org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource"};
    private Class<? extends DataSource> type;
    private ClassLoader classLoader;
    private Map<String, String> properties = new HashMap();

    public static DataSourceBuilder<?> create() {
        return new DataSourceBuilder((ClassLoader)null);
    }

    public static DataSourceBuilder<?> create(ClassLoader classLoader) {
        return new DataSourceBuilder(classLoader);
    }

    private DataSourceBuilder(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public T build() {
        Class<? extends DataSource> type = this.getType();
        DataSource result = (DataSource)BeanUtils.instantiateClass(type);
        this.maybeGetDriverClassName();
        this.bind(result);
        return result;
    }

    private void maybeGetDriverClassName() {
        if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
            String url = (String)this.properties.get("url");
            String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
            this.properties.put("driverClassName", driverClass);
        }

    }

    private void bind(DataSource result) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
        ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
        aliases.addAliases("url", new String[]{"jdbc-url"});
        aliases.addAliases("username", new String[]{"user"});
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
    }

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

    public DataSourceBuilder<T> url(String url) {
        this.properties.put("url", url);
        return this;
    }

    public DataSourceBuilder<T> driverClassName(String driverClassName) {
        this.properties.put("driverClassName", driverClassName);
        return this;
    }

    public DataSourceBuilder<T> username(String username) {
        this.properties.put("username", username);
        return this;
    }

    public DataSourceBuilder<T> password(String password) {
        this.properties.put("password", password);
        return this;
    }

    public static Class<? extends DataSource> findType(ClassLoader classLoader) {
        String[] var1 = DATA_SOURCE_TYPE_NAMES;
        int var2 = var1.length;
        int var3 = 0;

        while(var3 < var2) {
            String name = var1[var3];

            try {
                return ClassUtils.forName(name, classLoader);
            } catch (Exception var6) {
                ++var3;
            }
        }

        return null;
    }

    private Class<? extends DataSource> getType() {
        Class<? extends DataSource> type = this.type != null ? this.type : findType(this.classLoader);
        if (type != null) {
            return type;
        } else {
            throw new IllegalStateException("No supported DataSource type found");
        }
    }
}

解决方案一

  • 配置一个数据源
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import javax.sql.DataSource;

/**
 * @author xing yun
 * @功能
 */

@Configuration
public class MyDataSourceConfig {

    @Autowired
    DataSourceProperties dataSourceProperties;

    @Primary
    @Bean
    public DataSource dataSource(){
        HikariConfig hikariConfig=new HikariConfig();
        hikariConfig.setJdbcUrl(dataSourceProperties.getUrl());
        hikariConfig.setUsername(dataSourceProperties.getUsername());
        hikariConfig.setPassword(dataSourceProperties.getPassword());
        hikariConfig.setDriverClassName(dataSourceProperties.getDriverClassName());
        HikariDataSource hikariDataSource=new HikariDataSource(hikariConfig);
        return hikariDataSource;
    }
}

值得注意的我们通过操作DataSourceProperties 这个类就可以获取applicaion-dev.properties 里面如下变量的配置值。

  • spring.datasource.url
  • spring.datasource.username
  • spring.datasource.passowrd
  • spring.datasource.drivcer-class-name

解决方案二

当然网上还有一种写法同样可以获取application.properties 里面的值。

感谢前辈的博文 springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required)

代码做了精简后如下:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import javax.sql.DataSource;

/**
 * @author xing yun
 * @功能
 */

@Configuration
public class MyDataSourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Primary
    @Bean
    public DataSource dataSource(DataSourceProperties properties){
      return DataSourceBuilder.create(properties.getClassLoader())
				.type(HikariDataSource.class)
				.driverClassName(properties.determineDriverClassName())
				.url(properties.determineUrl())
				.username(properties.determineUsername())
				.password(properties.determinePassword())
				.build();
    }
}

这种写法可用于配置多种数据源。

  • @Primary :自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
  • 如果配置的是 @ConfigurationProperties(prefix = "spring.datasource")那么配置文件中就是
    • spring.datasource.url
    • spring.datasource.username
    • spring.datasource.password
  • 如果这里配置成 @ConfigurationProperties(prefix = "spring.datasource.one")那么配置文件中就是
    • spring.datasource.one.url
    • spring.datasource.one.username
    • spring.datasource.one.password

采取以上方案后虽然可以正常使用了,但是,健康检查还是通不过。 那就暂时注释掉它好了。


参考资料

  • After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName
  • springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required)

你可能感兴趣的:(StackOverflow)