Java Spring Boot:使用 database 和 application.properties 进行配置
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40465360/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot: Use database and application.properties for configuration
提问by deve
I need to save the configuration of the Spring Boot application in the database.
我需要在数据库中保存 Spring Boot 应用程序的配置。
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?
是否可以将数据库信息存储在 中application.properties
并使用它们连接到数据库并从那里检索所有其他属性?
So my application.properties
would look like:
所以我的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
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
.
与ApplicationConfiguration
作为Entity
。
But Spring Boot does not get the configuration from the database.
但是 Spring Boot 并没有从数据库中获取配置。
回答by kuhajeyan
One possible solution that you could workout, is to use ConfigurableEnvironmentand reload and add properties.
您可以锻炼的一种可能的解决方案是使用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));
}
}
回答by deve
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")
.
我从数据库加载配置并将其写入另一个属性文件。此文件可与@PropertySource("classpath:configuration.properties")
.
回答by samarone
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.
另一种选择是使用 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);
}
References:
参考:
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context
- https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-environment-or-application-context
- 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. ;)
PS:此代码是我在互联网上找到的其他代码的混合。学分完全来自他们的作者。很抱歉找不到他们的链接,在你让他们工作之前已经进行了很多测试。但是,如果您发现这与那里发现的其他一些相似,您可以确定这只是一个推导。;)
Environment:
环境:
- Java: OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
- Spring: 4.3.11
- Spring Boot: 1.5.7
- Hibernate Core: 5.2.10-Final
- Java:OpenJDK 运行时环境(构建 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
- 春天:4.3.11
- 弹簧靴:1.5.7
- Hibernate 核心:5.2.10-Final
回答by Tom Van Rossom
What you need is Spring Cloud Config: https://cloud.spring.io/spring-cloud-config/
您需要的是 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.
它将使用带有所有属性文件的 ad git 存储库(= 数据库)。在启动时,它将获得最新版本,并使用它来启动应用程序。
When you change the configuration at runtime, it is possible to refresh, without needing to restart!
当您在运行时更改配置时,可以刷新,而无需重新启动!
回答by David Harris
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 的实现。下面是我使用的一个实现,它对我来说效果很好。您必须使用以下条目将 META-INF/spring.factories 文件添加到您的部署中:
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
您可以从此处的文档中阅读更多相关信息:https: //docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment - 或应用程序上下文
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;
}
}
}
回答by Nikhil Pate
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.
如果数据库中的属性可以通过 @Value 注释获得怎么办?对的,这是可能的。
You just have to define a class which implements EnvironmentAware and add custom logic in setEnvironment method.
您只需要定义一个实现 EnvironmentAware 的类并在 setEnvironment 方法中添加自定义逻辑。
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.
定义一个 JPA 存储库以从数据库中获取配置。
@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.
您必须在要使用 @Value 注释的类上添加 @DependsOn 注释。
@DependsOn takes application configuration bean id as parameter so that our properties from database are loaded in environment before our custom beans load.
@DependsOn 将应用程序配置 bean id 作为参数,以便在加载自定义 bean 之前将数据库中的属性加载到环境中。
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.
请注意,在 application.properties 中添加了 JPA 配置。