简体   繁体   中英

Spring Boot multiple datasources with repeating properties

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM