5

I have a setup my spring web application where there are two data sources, the main one and the secondary one. These two data sources mostly share all configuration properties apart from username, password and url. As the common property list is growing I want to use the common configuration properties for both data sources and only specify which ones to override for the secondary data source and others. For example, I have setup my main data source bean like this:

@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
    return DataSourceBuilder.create().build();
}

And in the secondary data source:

@Value("${spring.secondaryDatasource.url}")
private String databaseUrl;

@Value("${spring.datasource.username}")
private String username;

@Value("${spring.datasource.password}")
private String password;

@Value("${spring.datasource.driver-class-name}")
private String driver;

@Bean
public DataSource secondaryDataSource() {
    return DataSourceBuilder
            .create()
            .url(databaseUrl)
            .username(username)
            .password(password)
            .driverClassName(driver)
            .build();
}

I've also setup an example project which is similar to my current setup: https://github.com/Edvinas01/MultipleDatasources

Is it possible to inject the repeating properties such as driver name and others while only specifying the ones to override? Something like this (this doesn't work):

@Bean
@ConfigurationProperties(prefix = "spring.datasource") // Inject default properties
public DataSource secondaryDataSource() {
    return DataSourceBuilder
            .create()
            .url(databaseUrl)   // Override url
            .username(username) // Override username
            .password(password) // Override  password
            .build();
}

Edit:

I've replaced my .properties file to .yml configuration file like so:

spring:
  jpa.hibernate.ddl-auto: update
  datasource:
    username: main
    password: main
    url: jdbc:hsqldb:mem:main
    driver-class-name: org.hsqldb.jdbc.JDBCDriver

---

spring:
  profiles: secondary
  datasource:
    username: secondary
    password: secondary
    url: jdbc:hsqldb:mem:secondary

---

spring:
  profiles.active: default,secondary

And the data source beans:

@Bean
@Primary
@Profile("default")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
@Profile({"secondary", "default"})
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

My main data source (default) gets the secondary data sources values except for the driver which is not specified in the secondary profile config. While the secondary data source gets the correct properties.

Is there a possible solution for this with without using .yml format for configurations or without having to create multiple .applications files for each profile?

Edit2: Some clarification, on our setup we have current properties files:

application.properties (sets the active profile according to the machine and common properties)
application-stating.properties (staging machine)
application-production.properties (production machine)

Both staging and production environments must use both data sources, so staging has two data sources (main, secondary) and production has two data sources (main, secondary). Settings such as drivers, and few others are shared between main and the secondary data source. The issue comes when trying to inject those common properties into the second data source.

2 Answers 2

3

Finally got this working with reusable properties for both data sources. First I created a bean to store these common properties:

@Configuration
public class CommonPropertiesConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public PoolProperties poolProperties() {
        return new PoolProperties();
    }
}

And in the second data source configuration I autowire the common properties (all data source properties) and inject specific properties for this data source by value:

@Value("${spring.secondaryDatasource.username}")
private String username;

@Value("${spring.secondaryDatasource.password}")
private String password;

@Value("${spring.secondaryDatasource.url}")
private String url;

@Autowired
private PoolProperties poolProperties;

@Bean
public DataSource secondaryDataSource() {
    DataSource dataSource = new DataSource(poolProperties);
    dataSource.setPassword(password);
    dataSource.setUsername(username);
    dataSource.setUrl(url);
    return dataSource;
}

And now the application.properties looks like this:

# Main/common data source properties.
spring.datasource.username=test
spring.datasource.password=test

spring.datasource.url=jdbc:hsqldb:mem:main
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver

# Override these properties for second datasource.
spring.secondaryDatasource.username=second
spring.secondaryDatasource.password=second
spring.secondaryDatasource.url=jdbc:hsqldb:mem:secondary

logging.level.com.datasources=DEBUG
spring.jpa.hibernate.ddl-auto=update

Changes are also reflected on the github repository.

Sign up to request clarification or add additional context in comments.

Comments

1

I recommend the usage of the Spring profiles in combination with YAML configuration (if possible, but it's adaptable on properties files).

// Insert in your Spring Configuration Class

   @Profile("!production")
   @ConfigurationProperties(prefix = "spring.datasource")
   @Bean
   public DataSource dataSource() {
       return DataSourceBuilder.create().build();
   }

   @Profile("production")
   @ConfigurationProperties(prefix = "spring.datasource")
   @Bean
   public DataSource secondDataSource() {
      return DataSourceBuilder
        .create()
        .url(databaseUrl)   // Override url
        .username(username) // Override username
        .password(password) // Override  password
        .build();
   }

If you start your application with the appropriate profile, e.g. second (see http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html), then your correct DataSource is loaded. The default profile needs no spring.profiles.active value set.

Edit:

There is no need to create additional profiles or files. Reminder: You can set a default profile via -Dspring.profiles.active="production". If no default profile is set, then it's default, which you don't have to create/define.

Ok, back to topic:

Let's say you want to work with a "production" and a "staging" profile and activate one configuration per profile. You can do it classically like above via replacing @Profile("default") with @Profile("default", "production") in the first bean. In the second bean, replace @Profile("second") with @Profile("staging").

The other way is to do it via a logical operator. For the "production" profile, you want to use the username/password datasource. So the an @Profile("production") annotation is needed here. For the non-productive usage (in your case staging), you can use @Profile("!production").

Nice to read: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html

Edit2:

For the solution with one properties / YAML file, I'd go to the following approach:

# application.yml
# default settings
spring:
  datasource:
    # insert your default settings
---
spring:
   profiles: staging
   datasource:
   # insert staging settings here
---
spring:
   profiles: production
   datasource:
   # insert production settings here

This saves you additional properties files for each profile.

4 Comments

I forgot to add that we've already setup a couple of profiles, one for staging and production, I think have few more profiles would complicate the situation even more. What I'm trying to achieve is to reduce the configuration files and properties.
I've tested out your suggestions, though I couldn't get it to work with .yml configuration file, maybe its too late... In the beginning I was thinking of going off with a single .properties file and just injecting the main data source properties into secondary data sources configuration class or something along those lines to avoid making multiple files. For multiple profiles, we set the environment according to the server where the application is running on via .properties files, hence the dislike of .yml
added further explanation for your requirements for just using one config file
Ok, I set the both profiles as active, "default", and "secondary". Sorry if forgot to add that I need both data sources active at the same time, they have all the same properties apart from URL and some other specific settings. I updated the repository with your suggestions and at the moment when having both profiles active, by adding some breakpoints I can see that the secondary profile takes over and overrides the default profile settings (username, password etc...). github.com/Edvinas01/MultipleDatasources/blob/master/src/main/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.