我需要在数据库中保存Spring Boot应用程序的配置。
是否可以将数据库信息存储在application.properties
中并使用它们连接到数据库并从那里检索所有其他属性?
所以我的application.properties
看起来像是:
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
其他配置将从数据库中获取,如下所示:
@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();
}
}
将ApplicationConfiguration
作为Entity
。
但Spring Boot无法从数据库中获取配置。
答案 0 :(得分:3)
您可以锻炼的一种可能的解决方案是使用ConfigurableEnvironment并重新加载和添加属性。
@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));
}
}
答案 1 :(得分:2)
我遗憾的是还没有针对此问题的解决方案,但我现在使用以下解决方法(需要在配置更改时重新启动其他应用程序)。
@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
}
}
}
我从数据库加载配置并将其写入另一个属性文件。此文件可与@PropertySource("classpath:configuration.properties")
一起使用。
答案 2 :(得分:2)
我知道这是一个老问题,但是在寻找解决方案时我偶然发现了这个问题,并想分享一种对我有用的方法。
一种实现方法是使用org.springframework.boot.env.EnvironmentPostProcessor的实现。下面是我使用的实现,对我来说效果很好。您必须使用以下条目将META-INF / spring.factories文件添加到您的部署中:
org.springframework.boot.env.EnvironmentPostProcessor=my.package.name.DBPropertiesLoaderEnvironmentPostProcessor
您可以从以下文档中了解有关此内容的更多信息: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;
}
}
}
答案 3 :(得分:2)
我知道这是一个老问题,但是这篇文章肯定可以帮助像我这样的人,他们正努力寻找确切的解决方案。
我们始终喜欢编写可配置的代码。
如果通过@Value注释可以使用数据库中的属性怎么办?是的,有可能。
您只需要定义一个实现EnvironmentAware的类并在setEnvironment方法中添加自定义逻辑即可。
让我们开始编码。
定义数据库实体。
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "app_config")
public class AppConfig {
@Id
private String configKey;
private String configValue;
}
定义一个JPA存储库以从数据库中获取配置。
@Repository
public interface AppConfigRepo extends JpaRepository<AppConfig, String> {
}
下面的代码会将数据库属性加载到应用程序环境中。
@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));
}
}
我们可以在系统环境下一级添加数据库属性,以便我们可以轻松覆盖而不接触数据库。下面的代码行帮助我们实现了相同的目标。
configurableEnvironment.getPropertySources().addAfter("systemEnvironment", new MapPropertySource("app-config", propertySource));
您必须在要使用@Value注释的类上添加@DependsOn注释。
@DependsOn将应用程序配置bean id作为参数,以便在加载自定义bean之前将数据库的属性加载到环境中。
所以,类看起来像这样
@Component
@DependsOn("applicationConfigurations")
public class SomeClass {
@Value("${property.from.database}")
private String property;
// rest of the code
}
请注意,JPA配置已添加到application.properties中。
答案 4 :(得分:1)
另一种选择是使用ApplicationContextInitializer,其优点是可以直接使用@Value并可以收缩属性的优先级。
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);
}
参考文献:
PS:此代码是我在互联网上发现的其他代码的组合。学分完全来自其作者。很抱歉,无法找到他们的链接,直到您使它们工作之前,已经进行了许多测试。但是,如果您发现它与那里的其他类似,则可以确定这只是一个推导。 ;)
环境:
答案 5 :(得分:0)
Spring Cloud Config Server支持JDBC(关系数据库)作为配置属性的后端。
Spring boot Config Server将在应用程序启动时从SQL数据库中提取属性。数据库需要有一个名为PROPERTIES的表。
点击链接获取更多详细信息:
https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_server.html
请参阅部分: 2.1.7 JDBC后端
答案 6 :(得分:-1)
您需要的是Spring Cloud Config:https://cloud.spring.io/spring-cloud-config/
它将对所有属性文件使用ad git存储库(=数据库)。在启动时,它将获取最新版本,并使用它来启动应用程序。
在运行时更改配置时,可以刷新而无需重新启动!