mattshma/bigdata

dataSource or dataSourceClassName or jdbcUrl is required

mattshma opened this issue · 1 comments

在配置 springboot2 多数据源时,将 application-prod.yaml 从单一数据源配置修改为多数据源,修改后 datasource 的配置如下:

spring:
    devtools:
        restart:
            enabled: false
        livereload:
            enabled: false
    datasource:
        primary:
            type: com.zaxxer.hikari.HikariDataSource
            url: jdbc:mysql://xxxxx
            username: ***
            password: ***
            hikari:
                auto-commit: false
                data-source-properties:
                    cachePrepStmts: true
                    prepStmtCacheSize: 250
                    prepStmtCacheSqlLimit: 2048
                    useServerPrepStmts: true
        secondary:
            type: com.zaxxer.hikari.HikariDataSource
            url: jdbc:mysql://yyyyyy
            username: ***
            password: ***
            hikari:
                auto-commit: false
                data-source-properties:
                    cachePrepStmts: true
                    prepStmtCacheSize: 250
                    prepStmtCacheSqlLimit: 2048
                    useServerPrepStmts: true

而 DataSourceConfig.java 如下:

@Configuration
public class DataSourceConfiguration {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSourceProperties() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

在运行时报错如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [config/LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1699)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:573)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:304)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:333)
	at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:157)
	at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:137)
	at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:91)
	at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:172)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5204)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1421)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1411)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
	at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:1063)
	at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:109)
	at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:385)

修改 application-prod.yaml 配置如下:

spring:
    devtools:
        restart:
            enabled: false
        livereload:
            enabled: false
    datasource:
        primary:
            type: com.zaxxer.hikari.HikariDataSource
            jdbcUrl: jdbc:mysql://xxxxx
            driver-class-name: com.mysql.jdbc.Driver
            username: ***
            password: ***
            hikari:
                auto-commit: false
                data-source-properties:
                    cachePrepStmts: true
                    prepStmtCacheSize: 250
                    prepStmtCacheSqlLimit: 2048
                    useServerPrepStmts: true
        secondary:
            type: com.zaxxer.hikari.HikariDataSource
            jdbcUrl: jdbc:mysql://yyyyyy
            driver-class-name: com.mysql.jdbc.Driver
            username: ***
            password: ***
            hikari:
                auto-commit: false
                data-source-properties:
                    cachePrepStmts: true
                    prepStmtCacheSize: 250
                    prepStmtCacheSqlLimit: 2048
                    useServerPrepStmts: true

若不指定 driver-class-name,会报错:java.lang.RuntimeException: Failed to get driver instance for jdbcUrl=jdbc:mysql

若报错:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available

可以检查如下几点:

  • PrimaryConfig.java 是否生效,主要查看:
    • 是否有 @Primary 注解,若有该注解的话,会自动生成 entityManagerFactory。
    • 是否 Secondary.Config.java 也配置了 @Primary 导致冲突。
    • 是否有 @Configuration 注解,导致 PrimaryConfig.java 文件没生效。
  • 报错的 domain 类,是否在 @primary 设置的 entityManagerFactorypackages() 搜索的目录中。
  • 设置的 @EnableJpaRepositories 中的 basePackages 用于配置 Repository 包位置,其路径是否正确。

若报错:

Caused by: java.lang.IllegalArgumentException: Not a managed type: class xxx

检查如下地方:

  • entity 类是否有注解 @Entity
  • entityManagerFactory 方法中是否指定了 packages("ENTITY_CLASS_PATH"),如:
    builder
             .dataSource(primaryDataSource)
             .properties(getVendorProperties())
             .packages("com.xyz.domain.primary")
             .persistenceUnit("primaryPersistenceUnit")
             .build();
    
  • 如果 entity 是分在 domain.primary 和 domain.secondary 两个目录中,且 PrimaryConfig.java 配置的 packages 和 SecondaryConfig.java 配置的 packages 分别在这两个 domain 目录中,则 repository 也需要放在两个目录中,不能混在一起,即:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "entityManagerFactoryPrimary",
    transactionManagerRef = "transactionManagerPrimary",
    basePackages = {"com.xyz.repository.primary"}    // 这里 repository 也需要放在 primary 目录中,否则加载时,会导致所有的 repository (包括 secondary repository 文件)也加载,进而导致报错。
)
public class PrimaryConfig {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Autowired
    private JpaProperties jpaProperties;

    private Map<String, Object> getVendorProperties() {
        return jpaProperties.getHibernateProperties(new HibernateSettings());
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(primaryDataSource)
            .properties(getVendorProperties())
            .packages("com.xyz.domain.primary")
            .persistenceUnit("primaryPersistenceUnit")
            .build();
    }

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }
}
  • 配置 @EntityScan(basePackages = {"com.xyz.domain.primary"})

若报错:Can't call commit when autocommit=true,则需要在 jdbcUrl: jdbc:mysql://xxxx?useUnicode=true&characterEncoding=utf&autoReconnect=true 后添加&relaxAutoCommit=true