Spring Boot:使用database和application.properties进行配置

时间:2016-11-07 12:36:33

标签: java spring spring-boot

我需要在数据库中保存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无法从数据库中获取配置。

7 个答案:

答案 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);
    }

参考文献:

  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:此代码是我在互联网上发现的其他代码的组合。学分完全来自其作者。很抱歉,无法找到他们的链接,直到您使它们工作之前,已经进行了许多测试。但是,如果您发现它与那里的其他类似,则可以确定这只是一个推导。 ;)

环境:

  1. Java:OpenJDK运行时环境(内部版本 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
  2. 春季:4.3.11
  3. Spring Boot:1.5.7
  4. 休眠核心:5.2.10-最终版

答案 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存储库(=数据库)。在启动时,它将获取最新版本,并使用它来启动应用程序。

在运行时更改配置时,可以刷新而无需重新启动!