Spring Boot externalised configuration for DataSource not working - spring

I have an application - using Spring 4.3.6 and Spring Boot 1.4.4 - which will be exported as a JAR. I want to connect to a remote Oracle database but I am having trouble externalising the configuration without breaking the application.
This is my current workaround:
import org.apache.tomcat.jdbc.pool.DataSource;
#Bean
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:oracle:thin:#ip-address:port:orcl");
dataSource.setUsername("user");
dataSource.setPassword("password");
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
return dataSource;
}
With the above, my application is able to connect to the database and execute queries successfully. However, when I try to externalise the configuration as follows:
#Bean
#ConfigurationProperties(prefix="app.datasource")
public DataSource dataSource() {
return new DataSource();
}
// application.properties
app.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
app.datasource.username=user
app.datasource.password=password
app.datasource.driver-class-name=oracle.jdbc.OracleDriver
I will get the following error when trying to execute jdbcTemplate.update(query) in my Spring Boot Controller (note that without externalising the above works):
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: The url cannot be null
I have tried to remove #ConfigurationProperties and change app.datasource to spring.datasource. I have also tried to use DataSourceBuilder.create().build() which returns javax.sql.DataSource but the same error is thrown in both cases.
I'm doing something wrong. What's the correct way to successfully externalise the configuration?

Suppose you have two datasources for two different Oracle databases. Then you have the following properties file:
/path/to/config/application.properties
oracle1.username=YourUser1
oracle1.password=YourPassword1
oracle1.url=jdbc:oracle:thin:#localhost:1521:XE
oracle2.username=YourUser2
oracle2.password=YourPassword2
oracle2.url=jdbc:oracle:thin:#192.168.0.3:1521:XE
Then in a configuration file:
import oracle.jdbc.pool.OracleDataSource;
#Configuration
public class DatasourcesConfig {
#Autowired
private Environment env;
#Primary
#Bean(name = "dataSource1")
DataSource oracle1DataSource() throws SQLException {
OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser(env.getProperty("oracle1.username"));
dataSource.setPassword(env.getProperty("oracle1.password"));
dataSource.setURL(env.getProperty("oracle1.url"));
return dataSource;
}
#Bean(name = "dataSource2")
DataSource oracle2DataSource() throws SQLException {
OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser(env.getProperty("oracle2.username"));
dataSource.setPassword(env.getProperty("oracle2.password"));
dataSource.setURL(env.getProperty("oracle2.url"));
return dataSource;
}
}
If you want to specify the external location of your application.properties file when running the jar, then set the spring.config.location as a system property, you can try:
java -jar target/your-application-0.0.1.jar -Dspring.config.location=/path/to/config/
Make sure the application.properties file is excluded when building the jar

There should be no need to create the DataSourceyourself.
Make sure you have the
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
Dependecy in your classpath and the oracle driver and put following properties in your application.properties file:
spring.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
After that you should be able to #Autowired your DataSource
For more information have a look at:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database

You cannot override the predefined properties provided by spring boot.
Just use the following properties in application.properties file.
spring.datasource.url=jdbc:oracle:thin:#ip-address:port:orcl
spring.datasource.data-username=user
spring.datasource.data-password=password
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
See Also : https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
Apart from above, to clarify #ConfigurationProperties is used at class level and the prefix "app" not "app.datasource"
#ConfigurationProperties(prefix = "app")
Now you have a class named DbProperties and the properties of the class is same as the last part of the key in application.properties
public class DBProperties {
private String url;
private String username;
private String password;
// setters and getters
}
Now the actual config/component class should look like following.
#Component
#ConfigurationProperties(prefix = "app")
public class MyComponent {
DBProperties datasource = new DBProperties();
public DBProperties getDatasource() {
return datasource;
}
public void setDatasource(DBProperties datasource) {
this.datasource = datasource;
}
}
Please note
The instance variable name is datasource which is same as the second part of the key
datasource is a class level instance

Related

How can I create a db2 connection in Spring boot without use application.properties?

I need to build a connection to db2 database, but I can not use application.properties at this moment, so how can I create this connection?
You can implement a datasource bean in the app itself. For example:
#Configuration
public class MyClass {
#Bean
public DataSource getDataSource() {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName(JDBC_DRIVER_CLASS_NAME);
dataSourceBuilder.url(myprops.getJDBCUrl());
dataSourceBuilder.username(myprops.getJDBCUsername());
dataSourceBuilder.password(myprops.getJDBCPassword());
return dataSourceBuilder.build();
}
}
Then, make sure any autowired references to the data source are "lazy", so the app has time to load the properties (maybe from another file) and to instantiate the data source, as in:
#Lazy
#Autowired
private DataSource dataSource;

h2 database config NOT read by springBoot IOC

I am working on some POC for which I am using h2 database with following details in application.properties file:
#datasource
ds.pc.jdbcUrl=jdbc:h2:mem:test;MODE=oracle;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
ds.pc.type=com.zaxxer.hikari.HikariDataSource
ds.pc.driver-class-name=org.h2.Driver
ds.pc.hikari.pool-name=pc-hikari-dbcp
ds.pc.hikari.maximum-pool-size=20
ds.pc.driver-type=thin
ds.pc.validation-query=Select 1 from dual
ds.pc.primary=true
ds.pc.username=sa
ds.pc.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2
I have defined my beans as below:
#Configuration
public class AppConfig
{
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public ConversionServiceFactoryBean conversionServiceFactoryBean()
{
return new ConversionServiceFactoryBean();
}
#Primary
#Bean(name = "pcJdbcTemplate")
public NamedParameterJdbcTemplate pCJdbcTemplate()
{
return new NamedParameterJdbcTemplate(pCDataSource());
}
#Primary
#Bean
#ConfigurationProperties(prefix = "ds.pc")
public DataSource pCDataSource()
{
return DataSourceBuilder.create().build();
}
}
It fails with below exception:
java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
at com.zaxxer.hikari.HikariConfig.validate(HikariConfig.java:958) ~[HikariCP-3.4.2.jar:?]
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:109) ~[HikariCP-3.4.2.jar:?]
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:158) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
But when I hard code the data source properties as below then it works:
#Primary
#Bean
public DataSource pCDataSource()
{
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("org.h2.Driver");
hikariConfig.setUsername("sa"); hikariConfig.setPassword("");
hikariConfig.setJdbcUrl(
"jdbc:h2:mem:test;MODE=oracle;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
return new HikariDataSource(hikariConfig);
}
Tried with changing properties as below:
ds.pc.jdbc-url=jdbc:h2:mem:test;MODE=oracle;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
ds.pc.url=jdbc:h2:mem:test;MODE=oracle;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
Still it doesn't work.
What's wrong in my application.properties file that it's not being read?
Also I am not able to see h2 console at localhost:8080/h2
The problem was that my application.properties file was under a folder named appConfig under src/main/resources.
Solution 1: Rename appConfig to config .
Go to the section 2.3. Application Property Files at documentation. It explains why config folder works.
Solution 2: move properties files directly under src/main/resources
Silly me. Had spent quite few hours to just find things can be simple.

Will spring Boot datasource properties work if we configure datasource programmatically in Spring Boot

Will Spring Boot datasource properties work if we configure datasource programmatically?
The following properties worked only when I fetch DB configuration from application.properties. If I configure datasource programmatically the following properties are not working.
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=5
I used the following code to configure datasource programmatically
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder.create().username(userName).password(password).url(url).driverClassName(driverName)
.build();
}
To make it work programmatically I used the following code snippet.But I'm not convinced. I feel it is not a cleaner solution. I have to read at least 20 properties from application.properties and add it to PoolProperties.
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Primary
public DataSource dataSource() {
PoolProperties poolProperties = new PoolProperties();
poolProperties.setUrl(url);
poolProperties.setDriverClassName(driverName);
poolProperties.setUsername(userName);
poolProperties.setPassword(password);
poolProperties.setTestWhileIdle(false);
poolProperties.setTestOnBorrow(true);
poolProperties.setValidationQuery("SELECT 1 FROM DUAL");
poolProperties.setTestOnReturn(false);
poolProperties.setValidationInterval(30000);
poolProperties.setTimeBetweenEvictionRunsMillis(30000);
poolProperties.setInitialSize(10);
poolProperties.setMaxActive(10);
poolProperties.setMaxIdle(5);
poolProperties.setMinIdle(5);
poolProperties.setMaxWait(10000);
poolProperties.setRemoveAbandonedTimeout(60);
poolProperties.setMinEvictableIdleTimeMillis(30000);
poolProperties.setLogAbandoned(true);
poolProperties.setRemoveAbandoned(true);
DataSource datasource = new DataSource(); // import org.apache.tomcat.jdbc.pool.DataSource;
datasource.setPoolProperties(poolProperties);
return datasource;
}
It there way we can make the following default Spring Boot properties work?
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=5
remember to give tomcat from properties for db in property files like this.
# Oracle DB - "foo"
spring.datasource.tomcat.url=jdbc:oracle:thin:#//db-server-foo:1521/FOO
spring.datasource.tomcat.username=fooadmin
spring.datasource.tomcat.password=foo123
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=5
then configure datasource like this.
/**
* Auto-configured DataSource
*/
#ConfigurationProperties(prefix = "spring.datasource.tomcat")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}

#Refreshscope with Datasource configuration

I am having a datasource configuration class in a Spring boot app. Snippet below
My configuration is fetched from Spring cloud config server. When I change my DB hostname and refresh using /refresh endpoint, the app is NOT using new DB host. ANy idea why ?
#Configuration
#RefreshScope
public classe DBConfig
{
#Resource
private Environment env;
private DataSource ehubDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("datasource.driverClassName"));
dataSource
.setUrl(env.getProperty("datasource.url"));
dataSource.setUsername(env.getProperty("datasource.username"));
dataSource.setPassword(env.getProperty("datasource.password"));
return dataSource;
}
}
As per docs,#RefreshScope will technically work on #Configuration, provided anything that depends on those beans cannot rely on them being updated when a refresh is initiated, unless it is itself in #RefreshScope
So could you please check your "Environment.java", You may forget to specify #RefreshScope in Environment.java. Please share your Environment.java if it is not working.
Normally, the #Configuration class contains beans, which means the datasource method should be marked as #Bean. You need #RefreshScope on each bean.
For a datasource, you probably want #ConfigurationProperties, rather than writing code for each property. #ConfigurationProperties automatically includes #RefreshScope, so you actually don't need RefreshScope here.
With #ConfigurationProperties almost no code is needed.
#Configuration
public class DBConfig
{
#Bean
#ConfigurationProperties("datasource")
public DataSource ehubDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
return dataSource;
}
}
If your Environment does something other than read the properties files, then this may not work for you.
If you want the bean name to be different from the method name, you can provide a parameter to #Bean. The code below creates the same bean as above.
#Bean(name = "ehubDataSource")
#ConfigurationProperties("datasource")
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
return dataSource;
}

How do you autowire/inject your datasource in Spring-boot?

I have been working with Spring boot for a bit now, and the datasource is always configured in your application.properties in every example I have seen, kind of like this:
# DataSource configuration
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/abcdef
spring.datasource.username=******
spring.datasource.password=******
However, lately I have been trying to integrate Spring Social, and the examples I have seen configure it in java in a config file like this:
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
This allows for the datasource object to later be injected or autowired into the social config as seen here for example.
My question is, do I need to configure a datasource bean like this to be able to later inject the datasource, or will Spring-boot handle that for me?
Not a Spring (or Boot) expert by any means, but Spring Boot will auto-provide a Bean of type DataSource if the properties are there and there's a requirement for it. To use it you just #Autowire it.
Try this . If there are multiple #Configuration in springboot , You can import the the other config(DataSourceConfig) into your main AppConfig.
And then Using #PropertySource pull in the db url,username,password etc
https://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch04s03.html
#Configuration
#Import(DataSourceConfig.class)
#PropertySource("classpath:application.properties")
public class SpringbatchConfig {
#Autowired
DataSourceConfig dataSourceConfig;
#Bean
public void myService myService() {
return new myServiceImpl(dataSourceConfig.dataSource());
}
}

Resources