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.
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.