spring-boothibernatespring-data-jpamultiple-databases

Spring-boot mysql multiple datasources: Getting "this.embeddedDatabaseConnection" is null Exception


You can clone the complete updated code from GitHub. Erroneous code which I had while posting this question is in the branch "issue_null_db_details_on_second_time_initialization"

I merged whatever I learnt from Spring boot How-to Guide to configure multiple data sources and to configure multiple entity managers and sample JPA code in official examples repository and finally have came up with below code.

Issue:

At first pass, the data source information provided under "application.properties" is properly being considered and

org.springframework.boot.autoconfigure.jdbc.initializeDataSourceBuilder

is properly built. But for some reason the same function is called for second time and this time, no information is being considered from "application.properties" and everything is null.

So, Spring boot thinks no external information is supplied and tries to configure as per the default configuration, which is to consider the application as Embedded Database. Since (of course) no Embedded DB configuration is supplied, I'm getting below Exception.

java.lang.NullPointerException: Cannot invoke "org.springframework.boot.jdbc.EmbeddedDatabaseConnection.getDriverClassName()" because "this.embeddedDatabaseConnection" is null

File: src/main/java/com/example/accessingdatamysql/TwoDataSources.java

@Configuration(proxyBeanMethods = false)
public class TwoDataSources {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first")
    public DataSourceProperties firstDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first.configuration")
    public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
        return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource) {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        DataSourceProperties firstDataSourceProps = firstDataSourceProperties();
        factoryBean.setDataSource(firstDataSource(firstDataSourceProps));
        factoryBean.setJpaVendorAdapter(vendorAdapter);
        factoryBean.setPackagesToScan(TwoDataSources.class.getPackage().getName());

        return factoryBean;
    }

    @Bean
    @ConfigurationProperties("app.datasource.second")
    public DataSourceProperties secondDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("app.datasource.second.configuration")
    public HikariDataSource secondDataSource(
            @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
        return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(DataSource secondDataSource) {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        DataSourceProperties secondDataSourceProps = secondDataSourceProperties();
        factoryBean.setDataSource(firstDataSource(secondDataSourceProps));
        factoryBean.setJpaVendorAdapter(vendorAdapter);
        factoryBean.setPackagesToScan(TwoDataSources.class.getPackage().getName());

        return factoryBean;
    }

}

File: src/main/resources/application.properties

spring.jpa.hibernate.ddl-auto=none

app.datasource.first.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/first_db
app.datasource.first.username=root
app.datasource.first.password=root
app.datasource.first.configuration.maximum-pool-size=30

app.datasource.second.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/second_db
app.datasource.second.username=root
app.datasource.second.password=root
app.datasource.second.max-total=30

Error Stack Trace:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.6.12)

2022-10-06 19:00:10.704  INFO 24638 --- [           main] c.e.a.AccessingDataMysqlApplication      : Starting AccessingDataMysqlApplication using Java 17.0.1 on padmahasa-desktop with PID 24638 (/media/padmahasa/personal/accessing-data-mysql-multiple-datasources/target/classes started by padmahasa in /media/padmahasa/personal/accessing-data-mysql-multiple-datasources)
2022-10-06 19:00:10.708  INFO 24638 --- [           main] c.e.a.AccessingDataMysqlApplication      : No active profile set, falling back to 1 default profile: "default"
2022-10-06 19:00:11.468  INFO 24638 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-10-06 19:00:11.524  INFO 24638 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 44 ms. Found 2 JPA repository interfaces.
2022-10-06 19:00:12.301  INFO 24638 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-10-06 19:00:12.315  INFO 24638 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-10-06 19:00:12.315  INFO 24638 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-10-06 19:00:12.489  INFO 24638 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-10-06 19:00:12.490  INFO 24638 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1702 ms
2022-10-06 19:00:12.684  WARN 24638 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstEntityManagerFactory' defined in class path resource [com/example/accessingdatamysql/TwoDataSources.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean]: Factory method 'firstEntityManagerFactory' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.boot.jdbc.EmbeddedDatabaseConnection.getDriverClassName()" because "this.embeddedDatabaseConnection" is null
2022-10-06 19:00:12.691  INFO 24638 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2022-10-06 19:00:12.709  INFO 24638 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-10-06 19:00:12.732 ERROR 24638 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstEntityManagerFactory' defined in class path resource [com/example/accessingdatamysql/TwoDataSources.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean]: Factory method 'firstEntityManagerFactory' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.boot.jdbc.EmbeddedDatabaseConnection.getDriverClassName()" because "this.embeddedDatabaseConnection" is null
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) ~[spring-context-5.3.23.jar:5.3.23]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908) ~[spring-context-5.3.23.jar:5.3.23]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.23.jar:5.3.23]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.12.jar:2.6.12]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:745) ~[spring-boot-2.6.12.jar:2.6.12]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:420) ~[spring-boot-2.6.12.jar:2.6.12]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.6.12.jar:2.6.12]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) ~[spring-boot-2.6.12.jar:2.6.12]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.6.12.jar:2.6.12]
    at com.example.accessingdatamysql.AccessingDataMysqlApplication.main(AccessingDataMysqlApplication.java:12) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean]: Factory method 'firstEntityManagerFactory' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.boot.jdbc.EmbeddedDatabaseConnection.getDriverClassName()" because "this.embeddedDatabaseConnection" is null
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.23.jar:5.3.23]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.23.jar:5.3.23]
    ... 19 common frames omitted
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.boot.jdbc.EmbeddedDatabaseConnection.getDriverClassName()" because "this.embeddedDatabaseConnection" is null
    at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:249) ~[spring-boot-autoconfigure-2.6.12.jar:2.6.12]
    at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.initializeDataSourceBuilder(DataSourceProperties.java:193) ~[spring-boot-autoconfigure-2.6.12.jar:2.6.12]
    at com.example.accessingdatamysql.TwoDataSources.firstDataSource(TwoDataSources.java:37) ~[classes/:na]
    at com.example.accessingdatamysql.TwoDataSources.firstEntityManagerFactory(TwoDataSources.java:46) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.23.jar:5.3.23]
    ... 20 common frames omitted

You can find the SQL Statements under "src/main/resources/sql_scripts.sql" (OR in GitHub)

May I know why at second pass no data source information is considered from "application.properties" and how to fix this issue?

Thank you.


Update 1:

As pointed out by @bcr666 in the comment, I shouldn't have created a new object for DataSourceProperties, instead, I injected the "firstDataSourceProps" to "firstEntityManagerFactory", like, public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource, DataSourceProperties firstDataSourceProps)

But now, I'm getting a new error.

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

Description:

A component required a bean named 'entityManagerFactory' that could not be found.


Action:

Consider defining a bean named 'entityManagerFactory' in your configuration.

Update 2:

As mentioned in this StackOverflow Answer, I should have named the second DB's EntitiManager bean as @Bean(name="entityManagerFactory"). Now the issue is resolved.

Thank you.


Solution

  • I think this is your problem DataSourceProperties firstDataSourceProps = firstDataSourceProperties();. I think you should remove the firstDataSourceProps to a different class, then autowire it in, instead of executing it yourself.