大家好,又见面了,我是你们的朋友全栈君。
故障错误
最近在使用Spring Boot 2.x with H2 Database 以及JPA整合一个项目的时候出现了下面这一个故障:
代码语言:javascript复制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
代码语言:javascript复制public class HikariDataSource extends HikariConfig implements DataSource{
...
public HikariDataSource(HikariConfig configuration) {
configuration.validate();
...
}
}
HikaruiDatasource.java中有一个构造方法,构造方法中调用了validate方法,而错误就发生在这个验证方法中。
HikariConfig.java
代码语言:javascript复制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
变量为空,那么就会抛出这个错误。
于是我尝试在代码里添加
代码语言:javascript复制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.url
和spring.datasource.jdbc-url
之间的关系。
核心如下:
代码语言:javascript复制 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
完整代码如下:
代码语言:javascript复制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)
代码做了精简后如下:
代码语言:javascript复制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
采取以上方案后虽然可以正常使用了,但是,健康检查还是通不过。 那就暂时注释掉它好了。 <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> -->
参考资料
- After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName
- springboot 2 Hikari 多数据源配置问题(dataSourceClassName or jdbcUrl is required)
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/152362.html原文链接:https://javaforall.cn