深入Spring Boot:怎样排查 Cannot determine embedded database driver class for database type NONE

写在前面

这个demo来说明怎么一步步排查一个常见的spring boot AutoConfiguration的错误。

https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-database-type-NONE

调试排查 Cannot determine embedded database driver class for database type NONE 的错误

把工程导入IDE里,直接启动应用,抛出来的异常信息是:


Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-11-29 14:26:34.478 ERROR 29736 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :

***************************
APPLICATION FAILED TO START
***************************

Description:

Cannot determine embedded database driver class for database type NONE

Action:

If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).


其实这时有两个思路,直接google搜索Cannot determine embedded database driver class for database type NONE,就可以找到解决办法。

第二种方式,仔细查看日志内容,可以发现有To display the auto-configuration report re-run your application with 'debug' enabled.

搜索下这个,就可以在spring的官方网站上找到相关的信息:https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html

就是用户只要配置了debug这个开关,就会把auto-configuration 相关的信息打印出来。

熟悉spring的环境变量注入的话,就可以知道有几种打开这个的方式:

  • args里增加--debug
  • 在application.properties里增加debug=true
  • 通过-Ddebug=true

增加debug开关之后的信息

增加debug开关之后,可以看到打印出了错误堆栈:

2017-11-29 14:33:08.776 DEBUG 29907 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : Application failed to start due to an exception

org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
	at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:245) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
	at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.initializeDataSourceBuilder(DataSourceProperties.java:182) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
	at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:42) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
	at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat.dataSource(DataSourceConfiguration.java:53) ~[spring-boot-autoconfigure-1.4.7.RELEASE.jar:1.4.7.RELEASE]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]

抛出异常的代码是:

	/**
	 * Determine the driver to use based on this configuration and the environment.
	 * @return the driver to use
	 * @since 1.4.0
	 */
	public String determineDriverClassName() {
		if (StringUtils.hasText(this.driverClassName)) {
			Assert.state(driverClassIsLoadable(),
					"Cannot load driver class: " + this.driverClassName);
			return this.driverClassName;
		}
		String driverClassName = null;

		if (StringUtils.hasText(this.url)) {
			driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
		}

		if (!StringUtils.hasText(driverClassName)) {
			driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
		}

		if (!StringUtils.hasText(driverClassName)) {
			throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection,
					this.environment, "driver class");
		}
		return driverClassName;
	}

可以看出来是没有找到 DataSource 的driver class,然后抛出了 DataSourceBeanCreationException

那么一种解决办法是,在maven依赖里加入一些 DataSource driver class。

但是应用自己的代码里并没有使用DataSource,哪里导致spring boot要创建一个DataSource对象?

哪里导致spring boot要创建DataSource

从异常栈上,可以找到DataSourceConfiguration$Tomcat 这个类,那么查找下它的引用,可以发现它是被org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.PooledDataSourceConfiguration import引入的。

	@Configuration
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
			DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
			DataSourceConfiguration.Generic.class })
	protected static class PooledDataSourceConfiguration {

	}

那么 PooledDataSourceConfiguration 是怎么生效的呢?从代码上可以看到@Conditional(PooledDataSourceCondition.class)

那么再看PooledDataSourceCondition的具体实现:

	/**
	 * {@link AnyNestedCondition} that checks that either {@code spring.datasource.type}
	 * is set or {@link PooledDataSourceAvailableCondition} applies.
	 */
	static class PooledDataSourceCondition extends AnyNestedCondition {

		PooledDataSourceCondition() {
			super(ConfigurationPhase.PARSE_CONFIGURATION);
		}

		@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
		static class ExplicitType {

		}

		@Conditional(PooledDataSourceAvailableCondition.class)
		static class PooledDataSourceAvailable {

		}

	}

PooledDataSourceCondition引入了@Conditional(PooledDataSourceAvailableCondition.class)

	/**
	 * {@link Condition} to test if a supported connection pool is available.
	 */
	static class PooledDataSourceAvailableCondition extends SpringBootCondition {

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			ConditionMessage.Builder message = ConditionMessage
					.forCondition("PooledDataSource");
			if (getDataSourceClassLoader(context) != null) {
				return ConditionOutcome
						.match(message.foundExactly("supported DataSource"));
			}
			return ConditionOutcome
					.noMatch(message.didNotFind("supported DataSource").atAll());
		}

		/**
		 * Returns the class loader for the {@link DataSource} class. Used to ensure that
		 * the driver class can actually be loaded by the data source.
		 * @param context the condition context
		 * @return the class loader
		 */
		private ClassLoader getDataSourceClassLoader(ConditionContext context) {
			Class<?> dataSourceClass = new DataSourceBuilder(context.getClassLoader())
					.findType();
			return (dataSourceClass == null ? null : dataSourceClass.getClassLoader());
		}

	}

从代码里,可以看到是尝试查找dataSourceClass,如果找到,条件就成立。那么debug下,可以发现查找到的dataSourceClass是:org.apache.tomcat.jdbc.pool.DataSource

那么再看下org.apache.tomcat.jdbc.pool.DataSource这个类是从哪里来的呢?

从maven依赖树可以看到,依赖是来自:spring-boot-starter-jdbc。所以是应用依赖了spring-boot-starter-jdbc,但是并没有配置DataSource引起的问题。

问题解决办法

有两种:

  1. 没有使用到DataSource,则可以把spring-boot-starter-jdbc的依赖去掉,这样就不会触发spring boot相关的代码
  2. 把spring boot自动初始化DataSource相关的代码禁止掉

禁止的办法有两种:

  1. 在main函数上配置exclude

        @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
    
  2. 在application.properties里配置:

        spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
    

总结

  1. 应用没有使用到DataSource,但是在pom.xml里引入了spring-boot-starter-jdbc
  2. spring-boot-starter-jdbc带入了tomcat-jdbc,它里面有org.apache.tomcat.jdbc.pool.DataSource
  3. spring boot里的PooledDataSourceConfiguration,判断classpath下面有DataSource的实现类,尝试去创建DataSource bean
  4. 在初始化DataSourceProperties时,尝试通过jdbc的url来探测driver class
  5. 因为应用并没有配置url,所以最终在DataSourceProperties.determineDriverClassName()里抛出Cannot determine embedded database driver class for database type NONE

最后:

  • 排查spring boot的AutoConfiguration问题时,可以按异常栈,一层层排查Configuration是怎么引入的,再排查Condition具体的判断代码。

公众号

欢迎关注公众号:横云断岭的专栏,专注分享Java,Spring Boot,Arthas,Dubbo。

横云断岭的专栏

已标记关键词 清除标记
#数据库配置 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.max-active=20 spring.datasource.max-idle=8 spring.datasource.min-idle=8 spring.datasource.initial-size=10 pom.xml配置依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </plugin> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>src\main\resources\templates\mybatisgenerator_config.xml</configurationFile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> 启动代码 package com.example.bookstore_demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @SpringBootApplication @MapperScan(basePackages = "com.example.bookstore_demo.dao") public class BookstoreDemoApplication { public static void main(String[] args) { SpringApplication.run(BookstoreDemoApplication.class, args); } } 报错Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled. 2017-11-13 13:52:17.858 ERROR 4136 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Cannot determine embedded database driver class for database type NONE Action: If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active). Disconnected from the target VM, address: '127.0.0.1:49367', transport: 'socket' Process finished with exit code 0
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页