How to Load Application Properties from Database
Every Java application whether its simple standalone or multi aspect complex enterprise java web application it will rely on some static properties file. This property file goes through constant changes by the hands of developers, deployment and configuration managers. Everyone changes the values in properties file as per their environment. We even end up keeping multiple properties file in our project. Each file will have environment specific values for example development, staging, quality, acceptance, pre-production and production.
Once code is moved to higher environment where application is deployed through some managed process, any change in the properties file has to be checked in to scm tools and then deployed to the targeted environment. In this article we will see the utility that will load the application properties from database. Any change in the property needs only application restart and you will get updated values from database.
Software used in this example
- Java 8
- MySQL
- Eclipse
We will first define our data source as below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/localdb" /> <property name="properties"> <props> <prop key="c3p0.acquire_increment">5</prop> <prop key="c3p0.maxStatementsPerConnection">20</prop> <prop key="c3p0.maxStatements ">100</prop> <prop key="c3p0.maxPoolSize">500</prop> <prop key="c3p0.max_statements">0</prop> <prop key="c3p0.minPoolSize">5</prop> <prop key="user">lessroot</prop> <prop key="password">********</prop> </props> </property> </bean> |
Once that is done, we will define our table that will hold key and value pairs for our application configuration
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE TABLE `localdb`.`config_params` ( `config_id` INT NOT NULL AUTO_INCREMENT, `config_key` VARCHAR(100) NULL, `config_value` VARCHAR(500) NULL, `config_description` VARCHAR(100) NULL, PRIMARY KEY (`config_id`), UNIQUE INDEX `config_key_UNIQUE` (`config_key` ASC)); --insert some sample values insert into config_params(config_key, config_value, config_description) values('con.key1', 'value1', 'description1'), ('con.key2', 'value2', 'description1'); |
Post this we will define our custom property processor class that will extend Spring’s PropertyPlaceholderConfigurer and will load properties from database
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.opencodez.util; import java.io.Serializable; import javax.sql.DataSource; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; public class PropertiesUtils extends PropertyPlaceholderConfigurer implements Serializable { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; private static CustomProperties customProps; private String dataSourceName; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DataSource dataSource = (DataSource) beanFactory.getBean(getDataSourceName()); customProps = new CustomProperties(dataSource); setProperties(customProps); super.postProcessBeanFactory(beanFactory); } public String getDataSourceName() { return dataSourceName; } public void setDataSourceName(String dataSourceName) { this.dataSourceName = dataSourceName; } public static String getProperty(String name) { return (null == customProps.get(name)) ? "" : customProps.get(name).toString(); } } |
Above class is configured as bean and it is supplied the datasource of our application. You can also note that we have set localOverride as true. This will make sure that properties from database take precedence over that available in any properties file.
1 2 3 4 5 6 7 8 9 |
<bean id="propertyConfigurer" class="com.opencodez.util.PropertiesUtils"> <property name="dataSourceName" value="dataSource" /> <property name="localOverride" value="true" /> <property name="locations"> <list> <value>classpath:app-custom.properties</value> </list> </property> </bean> |
And here is our custom properties class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class CustomProperties extends Properties { private final AppLogger logger = AppLogger.getInstance(); private static final long serialVersionUID = 1L; public CustomProperties(DataSource dataSource) { super(); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); List<Map<String, Object>> configs = jdbcTemplate .queryForList("select config_key, config_value from config_params"); logger.info("Loading properties from Database"); for (Map<String, Object> config : configs) { setProperty((config.get("config_key")).toString(), (config.get("config_value")).toString()); } } } |
This will make sure that you will have properties loaded from database as well as any additional configuration that you can safely put in application. You can access these properties directly using @Value annotation or you can call utility method provided along with the above class. We will see now our sample properties file and its output using test controller
1 2 3 4 |
env=${server.env} cron.frequency.jobwithsimpletrigger=2000 cron.frequency.jobwithcrontrigger=0/7 * * * * ? con.key2 = http://abc.com |
Sample Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.opencodez.controller; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.opencodez.util.AppUtil; import com.opencodez.util.PropertiesUtils; @RestController public class TestController { @Value("${con.key2}") String conKey2; @RequestMapping("/getval") public String getVal(@RequestParam(value="key", defaultValue="World") String key) { Map<String, String> mapOfKeyValue = new HashMap<String, String>(); mapOfKeyValue.put(key, PropertiesUtils.getProperty(key)); mapOfKeyValue.put("con.key2", conKey2); return AppUtil.getBeanToJsonString(mapOfKeyValue); } } |
And below is the output of our rest point http://localhost:8080/getval?key=con.key1
1 |
{"con.key2":"value2","con.key1":"value1"} |
We have properties value set from out database. Hope above code sample will be helpful to someone.
If you need to refer to the AppUtil class to run this application properties from database example, you can look in to one present in our Git Repo – AppUtil.java
Hello Pavan,
In your case you have hardcoded datasource property values within context file. I have a use case where datasource type and JNDI name is present in one property file and then rest of the property I need to load from that DB. How would you do that ?
Hi Dharm,
I think if you set localoverride to false and only keep two properties ds type and JNDI in this file and load rest from the database, it can address your use case.
Let me know if this works for you.
Hi, I was trying to implement this code, but couldnt find AppUtil class
Thanks for the update
In my case @value is not getting resolved. I do not have any file in the classpath, all my properties are in the database. I’m able to fetch them from DB but it is not getting resolved through @value
Thanks. It works well and very helpful. Just converted the xml to java config and here’s the propertyConfigurer bean in java config,
@Bean
public static PropertiesUtils getPropertyConfigurer() throws IOException{
PropertiesUtils propUtils = new PropertiesUtils();
propUtils.setDataSourceName(“dataSource”);
propUtils.setLocalOverride(true);
return propUtils;
}
Thanks Lakshman,
This could be helpful.
CustomProperties class, where it is defined?
Thanks Ankur. I did not realize I have missed to add that.
Please check now, I have added the code snippet.