如何解決運行springboot時報的錯誤: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).

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

其實這時有兩個思路,直接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]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

拋出異常的代碼是:

    /**
     * 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

可以看出來是沒有找到 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 {

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

那麼 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 {

        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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());
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

從代碼裏,可以看到是嘗試查找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具體的判斷代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章