akuma8
akuma8

Reputation: 4681

Spring Boot multiple configurations files with same names and same properties names

I am trying to setup a Spring Boot monolithic app which should behave like a microservice and I have some issues to manage configurations (.properties). This is how I organized the project (resources folder) : resources directory tree

As you can see there are common properties files : application.properties,application-dev.properties and application-prod.properties these properties should be shared by all sub-properties and they can eventually be overridden.

Each service has its own data source url and it also depending on the active profile : H2 for dev and MySQL for prod.

Here an exemple of how I manage configurations :

Contents of common application.properties:

spring.profiles.active=dev
spring.config.additional-location=classpath:productservice/, classpath:userservice/,classpath:financeservice/, classpath:securityservice/ #I am not sure this works...
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database=default
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none 
spring.jpa.properties.validation-mode=none 

Contents of common application-dev.properties:

spring.datasource.driver-class-name=org.h2.Driver
spring.h2.console.enabled=true
spring.h2.console.path=/db
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
spring.datasource.username=sa # We override data source username and paswword
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

Contents of common application-prod.properties:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Now for specific services :

productservice :

Content of application.properties:

spring.liquibase.change-log=classpath:productservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=productservicedb
spring.liquibase.check-change-log-location=true

Content of application-dev.properties:

spring.datasource.url=jdbc:h2:mem:productservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS productservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

Content of application-prod.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/productservicedb?useSSL=false

And for userservice I have :

Content of application.properties :

spring.liquibase.change-log=classpath:userservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=userservice
spring.liquibase.check-change-log-location=true

Content of application-dev.properties :

spring.datasource.url=jdbc:h2:mem:userservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS userservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

Content of application-prod.properties :

spring.datasource.url=jdbc:mysql://localhost:3306/userservicedb

And so on ... for other services.

To configure each data source I proceed like this :

productservice :

1- Retrieve properties from environment :

@Configuration( "productServiceProperties" )
@Getter
@Setter
@PropertySource( value = { "classpath:productservice/application.properties",
  "classpath:productservice/application-${spring.profiles.active}.properties" } )
public class ProductServiceProperties {

   @Autowired//I want `Environment` to contain properties from common properties + files in @PropertySource above
   private Environment environment;

    // ========DATASOURCE PROPERTIES=========
   private String      datasourceDriverClass;
   private String      datasourceUsername;
   private String      dataSourcePassword;
   private String      dataSourceUrl;

   // ========LIQUIBASE PROPERTIES==========
   private String      liquibaseChangeLog;
   private String      liquibaseDefaultSchema;

   @PostConstruct
   public void init() {
      this.datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
      datasourceUsername = environment.getProperty( "spring.datasource.username" );
      dataSourcePassword = environment.getProperty( "spring.datasource.password" );
      dataSourceUrl = environment.getProperty( "spring.datasource.url" );

      liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
      liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
      log.debug( "Initialisation {} ", datasourceDriverClass );
  }
}

2- Inject them in the DB configuration :

@Configuration
@EnableJpaRepositories( basePackages = "com.company.product.repository", entityManagerFactoryRef = "productServiceEntityManager", transactionManagerRef = "productServiceTransactionManager", considerNestedRepositories = true )
@EnableTransactionManagement( proxyTargetClass = false )
public class ProductServiceDBConfig {

    private static final String      packageToScan = "com.company.product.models";

    @Autowired
    private ProductServiceProperties productServiceProperties;

    @Bean( name = "productServiceDataSource" )
    public DataSource productServiceDataSource() {
       DriverManagerDataSource dataSource = new DriverManagerDataSource();
       dataSource.setDriverClassName( productServiceProperties.getDatasourceDriverClass() );
       dataSource.setUrl( productServiceProperties.getDataSourceUrl() );
       dataSource.setUsername( productServiceProperties.getDatasourceUsername() );
       dataSource.setPassword( productServiceProperties.getDataSourcePassword() );
       return dataSource;
    }

   @Bean
public JpaVendorAdapter productServiceVendorAdapter() {
    HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
    hibernateJpaVendorAdapter.setGenerateDdl( false );
    hibernateJpaVendorAdapter.setShowSql( true );
    return hibernateJpaVendorAdapter;
}

@Bean( name = "productServiceEntityManager" )
public LocalContainerEntityManagerFactoryBean productServiceEntityManager() {
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setDataSource( productServiceDataSource() );
    emf.setPackagesToScan( packageToScan );
    emf.setJpaVendorAdapter( productServiceVendorAdapter() );
    return emf;
}

@Bean( name = "productServiceTransactionManager" )
public PlatformTransactionManager productServiceTransactionManager() {
    JpaTransactionManager productTransactionManager = new JpaTransactionManager();
    productTransactionManager.setEntityManagerFactory( productServiceEntityManager().getObject() );
    return productTransactionManager;
}

@Bean
public SpringLiquibase productServiceLiquibase() {
    SpringLiquibase liquibase = new SpringLiquibase();
    liquibase.setDataSource( productServiceDataSource() );
    liquibase.setChangeLog( productServiceProperties.getLiquibaseChangeLog() );
    liquibase.setDefaultSchema( productServiceProperties.getLiquibaseDefaultSchema() );
    return liquibase;
  }
}

userservice :

1-

@Configuration( "userServiceProperties" )
@Getter
@Setter
@PropertySource( value = { "classpath:userservice/application.properties",
    "classpath:userservice/application-${spring.profiles.active}.properties" } ) //I want properties from properties file source above
public class UserServiceProperties {

    @Autowired //I want `Environment` to contain properties from common properties + files in @PropertySource above
    private Environment environment;

    // ========DATASOURCE PROPERTIES=========
    private String      datasourceDriverClass;
    private String      datasourceUsername;
    private String      dataSourcePassword;
    private String      dataSourceUrl;

    // ========LIQUIBASE PROPERTIES==========
    private String      liquibaseChangeLog;
    private String      liquibaseDefaultSchema;

    @PostConstruct
    public void init() {
        datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
        datasourceUsername = environment.getProperty( "spring.datasource.username" );
        dataSourcePassword = environment.getProperty( "spring.datasource.password" );
        dataSourceUrl = environment.getProperty( "spring.datasource.url" );

        liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
        liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
    }
}

2-

@Configuration
@EnableJpaRepositories( basePackages = "com.company.user.repository", entityManagerFactoryRef = "userServiceEntityManager", transactionManagerRef = "userServiceTransactionManager", considerNestedRepositories = true )
@EnableTransactionManagement( proxyTargetClass = false )
public class UserServiceDBConfig {

private static final String         packageToScan = "com.company.user.models";

   @Autowired
   private UserServiceProperties userServiceProperties;

   @Bean( name = "userServiceDataSource" )
   public DataSource userServiceDataSource() {
      DriverManagerDataSource dataSource = new DriverManagerDataSource();
      dataSource.setDriverClassName( userServiceProperties.getDatasourceDriverClass() );
      dataSource.setUrl( userServiceProperties.getDataSourceUrl() );
      dataSource.setUsername( userServiceProperties.getDatasourceUsername() );
      dataSource.setPassword( userServiceProperties.getDataSourcePassword() );
      return dataSource;
  }

  @Bean
  public JpaVendorAdapter userServiceVendorAdapter() {
      HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
      hibernateJpaVendorAdapter.setGenerateDdl( false );
      hibernateJpaVendorAdapter.setShowSql( true );
      return hibernateJpaVendorAdapter;
  }

  @Bean( name = "userServiceEntityManager" )
  public LocalContainerEntityManagerFactoryBean userServiceEntityManager() {
      LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
      emf.setDataSource( userServiceDataSource() );
      emf.setPackagesToScan( packageToScan );
      emf.setJpaVendorAdapter( userServiceVendorAdapter() );
      return emf;
  }

  @Bean( name = "userServiceTransactionManager" )
  public PlatformTransactionManager userServiceTransactionManager() {
      JpaTransactionManager userTransactionManager = new JpaTransactionManager();
      userTransactionManager.setEntityManagerFactory( userServiceEntityManager().getObject() );
      return userTransactionManager;
  }

  @Bean
  public SpringLiquibase userServiceLiquibase() {
      SpringLiquibase liquibase = new SpringLiquibase();
      liquibase.setDataSource( userServiceDataSource() );
      liquibase.setChangeLog( userServiceProperties.getLiquibaseChangeLog() );
      liquibase.setDefaultSchema( userServiceProperties.getLiquibaseDefaultSchema() );
      return liquibase;
   }
}

The problem is, as I @Autowired Environment to get my properties and all properties have same names, some properties always take precedence over other properties. E.g. the property spring.datasource.url contains the same value in both services.

I use Environment and not @Value neither @ConfigurationProperties because it gets properties basing on the active profile and that exactly what I want.

My questions are :

Thanks a lot (and sorry for the length of this post)

Upvotes: 3

Views: 5040

Answers (1)

akuma8
akuma8

Reputation: 4681

I solve the problem myself with the suggestion of Andy Wilkinson (@ankinson) on Twitter.

We can't have many properties files with same name. So I had to rename my sub configurations files like this :

product.properties
product-dev.properties
product-prod.properties

And so on...

Upvotes: 1

Related Questions