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
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));
}
}
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:
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:
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
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")
.
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.
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!