12

I need to save the configuration of the Spring Boot application in the database.

Is it possible to store the database information in the application.properties and use them to connect to the database and retrieve all the other properties from there?

So my application.properties would look like:

spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=mydb spring.datasource.username=user spring.datasource.password=123456 spring.jpa.database-platform=org.hibernate.dialect.SQLServer2012Dialect 

And the other configuration would be fetched from the database with something like this:

@Configuration @PropertySource(value = {"classpath:application.properties"}) public class ConfigurationPropertySource { private final ConfigurationRepository configurationRepository; @Autowired public ConfigurationPropertySource(ConfigurationRepository configurationRepository) { this.configurationRepository = configurationRepository; } public String getValue(String key) { ApplicationConfiguration configuration = configurationRepository.findOne(key); return configuration.getValue(); } } 

With ApplicationConfiguration as an Entity.

But Spring Boot does not get the configuration from the database.

11
  • you should be able to get the db configuration from properties file as you have written. do you see any error in console? Commented Nov 7, 2016 at 12:44
  • I get the configuration from the application.properties. And I can connect to the database and save Entities. But the properties from the database are not loaded. Commented Nov 7, 2016 at 12:49
  • then what does ConfigurationRepository do? Commented Nov 7, 2016 at 12:51
  • It retrieves the configuration. But Spring Boot does not "load" it on startup. For example the property spring.jpa.show-sql=true is saved in the database (along with other properties). If I set this property in the application.properties file it works fine, but if I put it in the database it has no effect on Spring Boot. Commented Nov 7, 2016 at 12:55
  • 1
    How can I tell Spring Boot to load the values from the database like the application.properties file? Commented Nov 7, 2016 at 12:56

8 Answers 8

4

One possible solution that you could workout, is to use ConfigurableEnvironment and reload and add properties.

@Configuration public class ConfigurationPropertySource { private ConfigurableEnvironment env; private final ConfigurationRepository configurationRepository; @Autowired public ConfigurationPropertySource(ConfigurationRepository configurationRepository) { this.configurationRepository = configurationRepository; } @Autowired public void setConfigurableEnvironment(ConfigurableEnvironment env) { this.env = env; } @PostConstruct public void init() { MutablePropertySources propertySources = env.getPropertySources(); Map myMap = new HashMap(); //from configurationRepository get values and fill mapp propertySources.addFirst(new MapPropertySource("MY_MAP", myMap)); } } 
Sign up to request clarification or add additional context in comments.

3 Comments

Unfortunately this did not work. The database contains configuration properties for sql logging. But no log was shown when I added a record to the database, so I suppose the properties are not loaded.
@deve Properties were loaded but after your logging was configured. Those properties from database can be used to configure Beans. Although I'd prefer to init them not in @PostConstruct but in custom BeanPostProcessor just after DataSource has been initiated
@EugeneTo could you please give an example?
4

I unfortunately don't have a solution for this problem yet, but I use the following workaround for now (requires an additional application restart on configuration change).

@Component public class ApplicationConfiguration { @Autowired private ConfigurationRepository configurationRepository; @Autowired private ResourceLoader resourceLoader; @PostConstruct protected void initialize() { updateConfiguration(); } private void updateConfiguration() { Properties properties = new Properties(); List<Configuration> configurations = configurationRepository.findAll(); configurations.forEach((configuration) -> { properties.setProperty(configuration.getKey(), configuration.getValue()); }); Resource propertiesResource = resourceLoader.getResource("classpath:configuration.properties"); try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propertiesResource.getFile()))) { properties.store(out, null); } catch (IOException | ClassCastException | NullPointerException ex) { // Handle error } } } 

I load the configuration from the database and write it to an another property file. This file can be used with @PropertySource("classpath:configuration.properties").

Comments

4

I know that this an old question but I stumbled on it when looking for a solution and wanted to share a way that worked for me.

One way you can do it is to use an implementation of org.springframework.boot.env.EnvironmentPostProcessor. Below is an implementation I used which has worked pretty well for me. You will have to add a META-INF/spring.factories file to your deployment with the following entry:

org.springframework.boot.env.EnvironmentPostProcessor=my.package.name.DBPropertiesLoaderEnvironmentPostProcessor 

You can read more about this from the docs here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context

package my.package.name; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.sql.DataSource; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException; import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import lombok.extern.slf4j.Slf4j; /** * This is used to load property values from the database into the spring application environment * so that @Value annotated fields in the various beans can be populated with these database based * values. I can also be used to store spring boot configuration parameters * * In order for Spring to use this post porcessor this class needs to be added into the META-INF/spring.factories file like so: * org.springframework.boot.env.EnvironmentPostProcessor=my.package.name.DBPropertiesLoaderEnvironmentPostProcessor * * It will look for the spring boot dataseource properties that traditionally get stored in the application.yml files and use * those to create a connection to the database to load the properties. It first looks for the datasource jndi name property * and if that fails it looks for the Spring.datasource.url based properties. * * */ @Slf4j public class DBPropertiesLoaderEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) { System.out.println("***********************************Pulling properties from the database***********************************"); if(env.getProperty("spring.datasource.jndi-name") != null) { log.info("Extracting properties from the database using spring.datasource.jndi-name"); try { JndiDataSourceLookup dsLookup = new JndiDataSourceLookup(); dsLookup.setResourceRef(true); DataSource ds = dsLookup.getDataSource(env.getProperty("spring.datasource.jndi-name")); try(Connection con = ds.getConnection()) { env.getPropertySources().addFirst(new DataBasePropertySource(con)); } log.info("Configuration properties were loaded from the database via JNDI Lookup"); } catch (DataSourceLookupFailureException | SQLException e) { log.error("Error creating properties from database with jndi lookup", e); e.printStackTrace(); } } else if(env.getProperty("spring.datasource.url") != null){ String url = env.getProperty("spring.datasource.url"); String driverClass = env.getProperty("spring.datasource.driver-class-name"); String username = env.getProperty("spring.datasource.username"); String password = env.getProperty("spring.datasource.password"); try { DriverManager.registerDriver((Driver) Class.forName(driverClass).newInstance()); try(Connection c = DriverManager.getConnection(url,username,password);){ env.getPropertySources().addFirst(new DataBasePropertySource(c)); log.info("Configuration properties were loaded from the database via manual connection creation"); } }catch(Exception e) { log.error("Error creating properties from database with manual connection creation.", e); } } else { log.error("Could not load properties from the database because no spring.datasource properties were present"); } } /** * An implementation of springs PropertySource class that sources from a * {@link DataBasedBasedProperties} instance which is java.util.Properties class that * pulls its data from the database.. * */ static class DataBasePropertySource extends EnumerablePropertySource<DataBasedBasedProperties> { public DataBasePropertySource(Connection c){ super("DataBasePropertySource",new DataBasedBasedProperties(c)); } /* (non-Javadoc) * @see org.springframework.core.env.PropertySource#getProperty(java.lang.String) */ @Override public Object getProperty(String name) { return getSource().get(name); } @Override public String[] getPropertyNames() { return getSource().getPropertyNames(); } } /** * Pulls name and value strings from a database table named properties * */ static class DataBasedBasedProperties extends Properties { private static final long serialVersionUID = 1L; private String[] propertyNames; public DataBasedBasedProperties(Connection con) { List<String> names = new ArrayList<String>(); try( Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select name, value from properties"); ){ while(rs.next()) { String name = rs.getString(1); String value = rs.getString(2); names.add(name); setProperty(name, value); } propertyNames = names.toArray(new String[names.size()]); }catch(SQLException e) { throw new RuntimeException(e); } } public String[] getPropertyNames() { return propertyNames; } } } 

Comments

4

I know that this an old question, but this post surely help someone like me who is struggling to find an exact solution.

We always love to write configurable code.

What if properties in database are available through @Value annotation ? Yes it is possible.

You just have to define a class which implements EnvironmentAware and add custom logic in setEnvironment method.


Let's start coding.

Define a database entity.

@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @Table(name = "app_config") public class AppConfig { @Id private String configKey; private String configValue; } 

Define a JPA repository to fetch configurations from database.

@Repository public interface AppConfigRepo extends JpaRepository<AppConfig, String> { } 

Below code will load database properties into application environment.

@Component("applicationConfigurations") public class ApplicationConfigurations implements EnvironmentAware { @Autowired private AppConfigRepo appConfigRepo; @Override public void setEnvironment(Environment environment) { ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment; Map<String, Object> propertySource = new HashMap<>(); appConfigRepo.findAll().stream().forEach(config -> propertySource.put(config.getConfigKey(), config.getConfigValue())); configurableEnvironment.getPropertySources().addAfter("systemEnvironment", new MapPropertySource("app-config", propertySource)); } } 

We can add our database properties one level below system environment so that we can easily override without touching the database. Below code line helps us to achieve the same.

configurableEnvironment.getPropertySources().addAfter("systemEnvironment", new MapPropertySource("app-config", propertySource)); 

You have to add @DependsOn annotation on class where you want to use @Value annotation.

@DependsOn takes application configuration bean id as parameter so that our properties from database are loaded in environment before our custom beans load.

So, class will look like this

@Component @DependsOn("applicationConfigurations") public class SomeClass { @Value("${property.from.database}") private String property; // rest of the code } 

Please note, JPA configurations are added in application.properties.

Comments

3

Another option is to use ApplicationContextInitializer, with the advantage of being able to use @Value directly and also being able to contract the precedence of the properties.

import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; public class ReadDBPropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { private static final Logger LOG = LoggerFactory.getLogger(ReadDBPropertiesInitializer.class); /** * Name of the custom property source added by this post processor class */ private static final String PROPERTY_SOURCE_NAME = "databaseProperties"; @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment configEnv = ((ConfigurableEnvironment) applicationContext.getEnvironment()); LOG.info("Load properties from database"); Map<String, Object> propertySource = new HashMap<>(); try { final String url = getEnv(configEnv, "spring.datasource.url"); String driverClassName = getProperty(configEnv, "spring.datasource.driver-class-name"); final String username = getEnv(configEnv, "spring.datasource.username"); final String password = getEnv(configEnv, "spring.datasource.password"); DataSource ds = DataSourceBuilder.create().url(url).username(username).password(password) .driverClassName(driverClassName).build(); // Fetch all properties PreparedStatement preparedStatement = ds.getConnection() .prepareStatement("SELECT config_key as name, config_value as value, config_label as label FROM TB_CONFIGURATION"); ResultSet rs = preparedStatement.executeQuery(); // Populate all properties into the property source while (rs.next()) { final String propName = rs.getString("name"); final String propValue = rs.getString("value"); final String propLabel = rs.getString("label"); LOG.info(String.format("Property: %s | Label: %s", propName, propLabel)); LOG.info(String.format("Value: %s", propValue)); propertySource.put(propName, propValue); } // Create a custom property source with the highest precedence and add it to // Spring Environment applicationContext.getEnvironment().getPropertySources() .addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource)); } catch (Exception e) { throw new RuntimeException("Error fetching properties from db"); } } private String getEnv(ConfigurableEnvironment configEnv, final String property) { MutablePropertySources propertySources = configEnv.getPropertySources(); PropertySource<?> appConfigProp = propertySources.get("applicationConfigurationProperties"); return System.getenv().get(((String) appConfigProp.getProperty(property)).replace("${", "").replace("}", "")); } private String getProperty(ConfigurableEnvironment configEnv, final String property) { MutablePropertySources propertySources = configEnv.getPropertySources(); PropertySource<?> appConfigProp = propertySources.get("applicationConfigurationProperties"); return (String) appConfigProp.getProperty(property); } 

References:

  1. https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context
  2. https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config

PS: This code is a mix of others I found on the internet. The credits are entirely from their authors. Sorry for not being able to find their links, there have been many tests until you get them to work. But if you find this similar to some other found out there, you can be sure that this is just a derivation. ;)

Environment:

  1. Java: OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
  2. Spring: 4.3.11
  3. Spring Boot: 1.5.7
  4. Hibernate Core: 5.2.10-Final

Comments

1

This is what works for me:

/** * Database configuration to access data * */ @Profile("!mock") @Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackages = {SomeOracleDataConfig.DOMAIN_REPO_PACKAGE}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "myTransactionManager") @EnableJdbcHttpSession public class SomeOracleDataConfig { static final String DOMAIN_REPO_PACKAGE = "com.my.app.repository.oracle"; private static final String DOMAIN_ENTITY_PACKAGE = "com.my.app.entity.oracle"; private static final String PERSISTENCE_UNIT_NAME = "ORA_MY_APP"; @Value("${spring.datasource.dialect:org.hibernate.dialect.Oracle12cDialect}") private String dialect; /** * A custom entity manager factory bean * * @param builder instance of {@link EntityManagerFactoryBuilder} * @param dataSource instance of {@link DataSource} * @return instance of {@link LocalContainerEntityManagerFactoryBean} */ @Primary @Bean(name = "myEntityManagerFactory") public LocalContainerEntityManagerFactoryBean myEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("myDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages(DOMAIN_ENTITY_PACKAGE) .persistenceUnit(PERSISTENCE_UNIT_NAME) .properties(singletonMap("hibernate.dialect", dialect)) .build(); } @Primary @Bean(name = "myTransactionManager") protected PlatformTransactionManager myTransactionManager( @Qualifier("myEntityManagerFactory") EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } /** * Creates Oracle data source configuration * * @return instance of {@link DataSource} */ @Primary @Bean(name = "myDataSource") @SpringSessionDataSource @ConfigurationProperties(prefix = "my.app.datasource") public DataSource myDataSource() { //Workaround to fix Oracle connection issue. For some reason -Duser.timezone parameter doesn't work TimeZone.setDefault( TimeZone.getTimeZone(DEFAULT_TIME_ZONE) ); return DataSourceBuilder.create().build(); } } 

And then the properties can be set like below:

# Oracle datasource configuration my.app.datasource.driverClassName=oracle.jdbc.driver.OracleDriver my.app.datasource.poolName=MYAPP-Pool my.app.datasource.minimum-idle=1 my.app.datasource.maximumPoolSize=5 my.app.datasource.dialect=org.hibernate.dialect.Oracle12cDialect my.app.datasource.jdbcUrl=<jdbc url> my.app.datasource.schema=<your schema> my.app.datasource.username=<username> my.app.datasource.password=<password> 

Comments

0

What you need is Spring Cloud Config: https://cloud.spring.io/spring-cloud-config/

It will use ad git repository (= database) with all the property files. At startup it will get the latest version, and uses this to launch the application.

When you change the configuration at runtime, it is possible to refresh, without needing to restart!

2 Comments

The question is asking to pull properties from a SQL Database, but you're suggesting to use an entirely different technology. Doesn't answer the question.
Indeed @George. I've assumed deve wanted a centralized place to store application properties, regardless of technology.
0

Spring Cloud Config Server supports JDBC (relational database) as a backend for configuration properties.

Spring boot Config Server will pull properties from a SQL Database on startup of your application. The database needs to have a table called PROPERTIES.

Following the link for more details:

https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_server.html

refer section: 2.1.7 JDBC Backend

1 Comment

That would be a nice solution when the properties could be loaded directly from the database. Here a Config Server is required. Unfortunately I can't run it in my environment.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.