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 !
Related
I've created a pretty standard Spring Boot 2.0 app with Services and Repositories to access a database.
I had to set the standard Spring Boot application properties to get it to work, such as:
spring.datasource.url, spring.jpa.database, etc.
However, in order to prevent my properties from overwriting other properties in similar apps hosted in the same place, I need to rename these properties, such as:
myApp.spring.datasource.url, myApp.spring.jpa.database, etc.
Some of these properties will be set by environmental variables instead of the application.properties file.
However, I can't see any way to override those variables in my app.
The standard approach is to use #Value to configure those variables. However, the Spring Boot 2.0 setup for services looks up all these properties "behind the scenes," so that doesn't appear to be an option here.
Is there any way to configure my app to read all those myApp.common.property.name properties and treat them as common.property.name?
Thank you.
Yes, the standard way is to use #Value. But thw work doesn't stop there, you need to create DataSource and EntityManager with these values.
Springboot will create DataSource, Entitymanager and some other components bey looking into default properties(spring.xxx) from the file(hence Spring boot is opinionated). But when you change these names to non default values, then you need to create these components / beans yourself.
Instead of using #Value you could also use #Configurationproperties. #Value also works but you might need to declare like 6 or 7 values with #Value. If you wish to use #ConfigurationProperties, make sure you have #EnableConfigurationProperties annotation added somewhere in your project.
Here is a code snippet. You need to tune it to your project
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.xxx.yyy.repo" }
)
public class SomeDbConfig {
#Primary // Use this if you have multiple datasources or else no use
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "myApp.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.xxx.yyy.domain")
.persistenceUnit("somename")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
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
Here is the problem, I need to configure additional datasource in my project.
There was only one datasource before, so I just configured one single datasource in application.properties, and then i coded like this:
#Repository
public class DrClusterManagerImpl implements DrClusterManager {
private final SqlSession sqlSession;
public DrClusterManagerImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
}
it works fine, thanks to springboot, but now I'm puzzled that how can i inject a specific sqlsession into this bean?
I've checked springboot reference, but it's not helpful.
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-two-datasources
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.
When my SpringApplicationContext is being boostrapped together, I want to get some values for that out of a properties file. From what I've read, it's best to use PropertySourcesPlaceholderConfigurer for this. I'm creating that bean in my spring java config and it is properly resolving the properties.
The problem is when I'm trying to use those properties in the construction of my other spring beans, like in my hibernate config. In my hibernate config, I have the following field:
#Value(value = "${myapp.database-access.url}")
public static String DB_ACCESS_URL;
Then I use that property to declare the url to access the database in my datasource bean like this:
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(DRIVER_CLASS_NAME);
dataSource.setUrl(DATABASE_URL);
dataSource.setUsername(DATABASE_USERNAME);
dataSource.setPassword(DATABASE_PASSWORD);
return dataSource;
}
Unfortunately the value is null at that point so my application bootstrapping fails. Post-construction the value is available, but apparently not mid-construction. Is there a way to get the #Value annotation to work for me in this circumstance, or do I have to use something else?
If I have to use something else, does anyone have any suggestions?
Spring does not allow injecting to static fields.
One approach:
public class Config {
public static String DB_ACCESS_URL;
#Value(value = "${myapp.database-access.url}")
public void setDbAccessUrl(String dbAccessUrl) {
Config.DB_ACCESS_URL = dbAccessUrl;
}
}
I think this is similar to the issue I was facing with the #PropertySource annotation where #Value wasn't injected in #Configuration POJOs. There are 2 ways I found to solve this:
1) Create a Spring XML config (if you don't have one already) which you'll refer to at your #Configuration class using #ImportResource and have in there a single entry: <context:property-placeholder />. That config should make #Value injection on the #Configuration class to work.
2) Don't use #Value in the #Configuration POJO but instead inject Environment and retrieve the props from there:
#Autowired Environment env;
//and further down at your dataSource bean retrieve the properties
//...
env.getProperty("myapp.database-access.url");
This is late but you can simply use #Value with constructor injection public DataSource dataSource(#Value(value = "${myapp.database-access.url}"))