How can I make a Spring Boot project with multiple data sources? - java

I'm working on a Spring Boot/Spring Batch project, and I need to configure two data sources. One is an in-memory hsqldb database used for tracking transactions. The other is a regular MySQL database which will be updated by my ItemWriters.
The problem is that as soon as I try to configure the second datasource, Spring starts throwing 'unresolvable circular dependency' errors, i.e.
Error creating bean with name 'preprodDataSource' defined in class path
resource [xxx/tools/batch/xxx/MyConfiguration.class]: Initialization of
bean failed; nested exception is
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error
creating bean with name 'dataSourceAutoConfigurationInitializer': Requested bean is
currently in creation: Is there an unresolvable circular reference?
The relevant chunk of my MyConfiguration.java file looks like:
#Bean
public DataSource transactionsDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true;hsqldb.tx=mvcc");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
#Bean
public DataSource preprodDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/somedb");
dataSource.setUsername("someuser");
dataSource.setPassword("somepass");
return dataSource;
}
If I comment out the #Bean defining the second data source, everything is fine. The application starts up and runs without problems. However, if I leave it in, I get the error above.
My naive interpretation of this is that Spring is building an instance of 'dataSourceAutoConfigurationInitializer' to handle initialization of the first datasource and that when it tries to construct a second to handle the second datasource, bad things happen.
Is there any way to work around this?

By default, Spring Boot's auto-configuration will try to create a JdbcTemplate for you using your application's DataSource. As you have configured two, it doesn't know which one to use. To tell it which one to use you should mark one of them as #Primary:
#Bean
#Primary
public DataSource transactionsDataSource() {
BasicDataSource dataSource = new BasicDataSource();
…
return dataSource;
}
Alternatively, you could disable the auto-configuration.

Related

"BasicBatchConfigurer" has protected access - Spring Batch/Spring Boot not persisting data to database

I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.

Spring Boot Weblogic 12c JNDI DataSources: injection not working gives NullPointerException

I had asked a question here but was incomplete and was marked as duplicate here
So based on the question already asked - one particular answer by #surasin-tancharoen seemed to be what I needed.
However trying that too gives me a NullPointerException since the data source is never created / injected.
Here are the details:
In the below code I have defined 2 beans. I have defined both datasources with #Qualifier annotation and #ConfigurationProperties to read the JNDI name from properties file.
#Configuration
public class DataSourceConfig {
#Bean
#Primary
#Qualifier("ds1")
#ConfigurationProperties(prefix="spring.datasource1")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Qualifier("ds2")
#ConfigurationProperties(prefix="spring.datasource2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
In application.properties:
spring.datasource1.jndi-name=AbcDS
spring.datasource2.jndi-name=XyzDS
Then in my DAO - I am trying to use this datasource:
#Autowired
#Qualifier("ds1")
DataSource dataSource;
However the datasource is not injected since I get a NullPointerException at this line of code:
conn = dataSource.getConnection();
All of this is being attempted by deploying the Spring Boot application to Weblogic 12c
The solution is related to incorrect usage of 'DataSourceBuilder' which should not be used for a JNDI datasource.
Here is how I got it working :
( deploying spring boot war on weblogic and consuming datasources defined in weblogic )
First we specify the datasources defined in weblogic - here we specify the JNDI name of the datasources defined in weblogic:
spring.datasource.xyz.jndi-name=XYZDS
spring.datasource.test.jndi-name=TESTDS
This is where the Datasource is created using above defined properties and exposed:
We are injecting the properties from application.properties into string variables to store JNDI names of datasources.
#Configuration
public class DataSourceConfig {
#Value( "${spring.datasource.xyz.jndi-name}" )
private String xyzJndiName;
#Value( "${spring.datasource.test.jndi-name}" )
private String testJndiName;
#Bean(name = "XyzDataSource")
public DataSource getXyzDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(xyzJndiName);
}
#Bean(name = "TestDataSource")
public DataSource getTestDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(testJndiName);
}
A few key points to note :
We should not be using 'DataSourceBuilder' for a JNDI datasource - it will not work.
This was the reason why it was not working in my case .
Now I am using 'JndiDataSourceLookup' which does the trick.
Another thing to note is that we need to use the standard #Bean annotation with the attribute 'name'.
This will be important in the piece of code that consumes this datasource.
So now the datasources are created successfully and exposed.
OK time to consume :
#Repository
public class SomeDaoImpl {
#Autowired
#Qualifier("XyzDataSource")
DataSource dataSource;
#Override
public List <String> create(Employee request) {
Connection conn = null;
conn = dataSource.getConnection();
Here we are using the #Qualifier annotation to pickup the appropriate datasource .
Thats it - this works now.
I tried it with the other data source - it worked as well.
NOTE:
I would not like to accept my own answer - so I will wait for a couple of days if anyone else has a better and more elegant solution please answer - will be happy to accept your answer rather than answering my own question and accepting !

Mark externally-declared bean as #Primary

I have a Spring Boot app that requires multiple datasources that will be used via JdbcTemplates to make sql statements to different DBs. These datasource beans are already declared in external dependencies that I am pulling into my app via an #Import statement the JdbcTemplate-bean-declaring java-based #Configuration classes.
After declaring two different JdbcTemplates initialized with two different DataSource beans form these external dependencies, I run into the following error on app startup:
Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found:
Given that I don't have access to the declarations of these DataSource beans in my app, how can I mark one as #Primary?
EDIT: Here's code for the construction of the JdbcTemplates (Note "dataSourceA" and "dataSourceB" are pulled from an external dependency)
#Configuration
#Import({DataSourceAApplicationConfig.class, DataSourceBApplicationConfig.class})
public class JdbcTemplateAppConfig {
#Autowired
DataSource dataSourceA;
#Autowired
DataSource dataSourceB;
#Bean
#Primary
public JdbcTemplate jdbcTemplateForDataSourceA(#Qualifier("dataSourceA") DataSource dataSource) {
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
#Bean
public JdbcTemplate jdbcTemplateForDataSourceB#Qualifier("dataSourceB") DataSource dataSource){
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
}

Change Spring config connection in runtime

I need to change my connection with the Database in runtime. For Example, if parameter requested is BD1, connect on database 1, if BD2, connect in database 2, etc.
I am using spring boot. What is the best way for this.
I have this #Configuration, but not know to say my repository how to use.
#Configuration
public class DataSourceConfiguration {
#Bean(name = "ccteste")
#ConfigurationProperties("spring.ciclocairu.teste.datasource")
#Primary
public DataSource ciclocairuTeste() {
return DataSourceBuilder.create().build();
}
#Bean(name = "ccprod")
#ConfigurationProperties("spring.ciclocairu.prod.datasource")
public DataSource ciclocairuProd() {
return DataSourceBuilder.create().build();
}
#Bean(name = "tmccteste")
#Autowired
#Primary
DataSourceTransactionManager transactionManagerCicloCairuTeste(#Qualifier("ccteste") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
#Bean(name = "tmccprod")
#Autowired
#Primary
DataSourceTransactionManager transactionManagerCicloCairuProd(#Qualifier("ccprod") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
}
Looks like you are looking for some data source routing. Spring has AbstractRoutingDataSource for run-time detection what data source should be used.
Abstract DataSource implementation that routes getConnection() calls
to one of various target DataSources based on a lookup key.
Also you can set default datasource by setDefaultTargetDataSource method.
It works in such way: you put data sources that you need in a map in AbstractRoutingDataSource at the bean configuration stage, and when you need to use a specific data source you put the key for this source into the context that linked to the router. This ds-key is linked to the current thread.
Here are examples : Dynamic DataSource Routing with Spring and Spring DataSource Routing
Might take a look at my answer at Manage transactions with multiple datasource, entity managers for same application code
or my blog post: Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres

Spring Boot. Get bean as response to local bean method call

I work on #Configuration class and masterTransactionManager bean needs to be injected with masterDataSource bean. I found example https://www.codeday.top/2017/07/08/31074.html and it not works.
Here simplified example class
#Configuration
public class MasterDataSourceConfig {
#Bean
#Primary
public DataSource masterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager() {
/*
* Spring not injects bean here, instead
* it just get new instance
* of DataSource object
*/
return new DataSourceTransactionManager(masterDataSource());
}
}
I was able to fix this with passing bean reference as argument:
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager(
#Qualifier("schmodelAuditDataSource") DataSource dataSource) {
/*
* Now bean injected, and everything works as it should
*/
return new DataSourceTransactionManager(dataSource);
}
Now the question is: how could it work new DataSourceTransactionManager(masterDataSource())? I never saw before that Spring can return bean on method call like in first example. Is this proper solution to get the bean? If this proper call, then why it not works for me?
I was not able to find a lot about such bean call method, though in thread Spring boot bean into bean injection methodology mentioned it should work. While in my case it is not.
Update: what error I see
The error I get in first case is
Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException
also in debug mode I see the instance of bean on spring bean initiation call is different, from the returning when my class calls masterDataSource() method.
The application starts without errors. I get error when start using beans instantiated (write data to database). I think this is result of not proper bean (is it bean at all, not sure) returned when called masterDataSource()
Before answer the question I need to clear what is Autowiring?
Spring managed their bean and lifecycle inside application context which is a container. Application context is a container which contain bean.
When the application context bootstrapped the beans also instantiated by their defined scope (singleton, prototype, request, session, global-session).
Spring's default scope is singleton refers it instantiate at once and shared the object (usually a cached object) within application context.
Autowire happens inject a beans instance to another bean.It means where we inject and what we inject -- both should be beans and lived in Spring IoC container (Application Context).
Now come to you question. If you autowire a bean, in fact your instantiated object (instantiated by spring) is set here.If you call some method like masterDataSource() in your example:-
#Bean
#Primary
public DataSource masterDataSource() {
DruidDataSource dataSource = new DruidDataSource();
...
return dataSource;
}
#Bean
#Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(masterDataSource());
}
Then masterDataSource() also create a new object and set (Injected) to DataSourceTransactionManager constructor.
So bottom line is if you used #Autowired then you get spring managed (lifecycle) bean/object and if you called masterDataSource() then you just create a new object apart from spring managed object.

Categories