【问题标题】:Spring Boot change DataSource and JPA properties at runtimeSpring Boot 在运行时更改 DataSource 和 JPA 属性
【发布时间】:2017-03-17 12:48:03
【问题描述】:

我正在编写一个桌面 Spring Boot 和 Data-JPA 应用程序。
初始设置来自application.properties(一些spring.datasource.*spring.jpa.*
我的程序的功能之一是可以通过 ui 指定数据库设置(rdbms 类型、主机、端口、用户名、密码等)。
这就是为什么我想在运行时重新定义已经初始化的数据库属性。 这就是为什么我正在寻找一种方法来做到这一点。

我尝试执行以下操作:
1) 我编写了自定义 DbConfig,其中在 Singleton Scope 中声明了 DataSource bean。

@Configuration
public class DBConfig {

  @ConfigurationProperties(prefix = "spring.datasource")
  @Bean
  @Scope("singleton")
  @Primary
  public DataSource dataSource() {
    return DataSourceBuilder
            .create()
            .build();
  }

}

2) 在一些 DBSettingsController 中,我得到了这个 bean 的实例并更新了新设置:

public class DBSettingsController {
   ...
   @Autowired DataSource dataSource;
   ...

   public void applySettings(){

       if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource){
        org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
        PoolConfiguration poolProperties = tomcatDataSource.getPoolProperties();
        poolProperties.setUrl("new url");
        poolProperties.setDriverClassName("new driver class name");
        poolProperties.setUsername("new username");
        poolProperties.setPassword("new password");
       }
   }
}

但它没有效果。 Spring Data Repositories 是使用已初始化的 DataSource 属性的钢材。

我还听说过Spring Cloud Config@RefreshScope。但我认为在我的小型桌面应用程序旁边运行 http 网络服务器是一种开销。

是否可以为此类 bean 编写自定义范围? 或者通过某种方式绑定在application.properties 和相应的bean 属性中所做的更改?

【问题讨论】:

  • @Mario ,它看起来像重复但实际上没有帮助,如果我理解正确AbstractRoutingDataSource 有助于以防有两个或多个静态预定义数据源并且需要在它们之间切换运行。我的情况有点不同。我只有一个数据源,我需要在运行时更改它的属性。除此之外,我还需要更改 JPA 属性。
  • 您的情况不同,但解决方法与建议的答案相同。您的问题是您必须告诉 bean 哪些是它们必须获取的数据源,而答案会告诉您一种自定义此行为的方法。请查看 AbstractRoutingDataSource 的文档。
  • 好的。其实你是对的。 AbstractRoutingDataSource 帮助了我。
  • 您能分享一下您的解决方案吗?我有点被同样的问题困住了

标签: java spring spring-boot spring-data spring-data-jpa


【解决方案1】:

这是我的解决方案(它可能已经过时,因为它是在 2016 年创建的):

DbConfig(其实不需要,我只是为了完整性配置添加的)

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;


@Configuration
public class DBConfig extends HibernateJpaAutoConfiguration {

    @Value("${spring.jpa.orm}")
    private String orm; // this is need for my entities declared in orm.xml located in resources directory


    @SuppressWarnings("SpringJavaAutowiringInspection")
    public DBConfig(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) {
        super(dataSource, jpaProperties, jtaTransactionManagerProvider);

    }

    @Override
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder factoryBuilder)
    {
        final LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = super.entityManagerFactory(factoryBuilder);
        entityManagerFactoryBean.setMappingResources(orm);
        return entityManagerFactoryBean;
    }
}

数据源配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    @Qualifier("default")
    @ConfigurationProperties(prefix = "spring.datasource")
    protected DataSource defaultDataSource(){
        return DataSourceBuilder
                .create()
                .build();
    }

    @Bean
    @Primary
    @Scope("singleton")
    public AbstractRoutingDataSource routingDataSource(@Autowired @Qualifier("default") DataSource defaultDataSource){
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.addDataSource(RoutingDataSource.DEFAULT,defaultDataSource);
        routingDataSource.setDefaultTargetDataSource(defaultDataSource);
        return routingDataSource;
    }
}

我对 RoutingDataSource 的扩展:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


public class RoutingDataSource extends AbstractRoutingDataSource {

    static final int DEFAULT = 0;
    static final int NEW = 1;

    private volatile int key = DEFAULT;

    void setKey(int key){
        this.key = key;
    }

    private Map<Object,Object> dataSources = new HashMap();

    RoutingDataSource() {
        setTargetDataSources(dataSources);
    }

    void addDataSource(int key, DataSource dataSource){
        dataSources.put(new Integer(key),dataSource);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return new Integer(key);
    }

    @Override
    protected DataSource determineTargetDataSource() {
        return (DataSource) dataSources.get(key);
    }
}

这里是特殊的 spring 组件,用于在运行时切换数据源:

import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

@Component
public class DBSettingsSwitcher {

    @Autowired
    private AbstractRoutingDataSource routingDataSource;

    @Value("${spring.jpa.orm}")
    private String ormMapping;

    public void applySettings(DBSettings dbSettings){

        if (routingDataSource instanceof RoutingDataSource){
            // by default Spring uses DataSource from apache tomcat

            DataSource dataSource = DataSourceBuilder
                    .create()
                    .username(dbSettings.getUserName())
                    .password(dbSettings.getPassword())
                    .url(dbSettings.JDBConnectionURL())
                    .driverClassName(dbSettings.driverClassName())
                    .build();

            RoutingDataSource rds = (RoutingDataSource)routingDataSource;

            rds.addDataSource(RoutingDataSource.NEW,dataSource);
            rds.setKey(RoutingDataSource.NEW);

            updateDDL(dbSettings);
        }
    }

    private void updateDDL(DBSettings dbSettings){

        /** worked on hibernate 5*/
        StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                .applySetting("hibernate.connection.url", dbSettings.JDBConnectionURL())
                .applySetting("hibernate.connection.username", dbSettings.getUserName())
                .applySetting("hibernate.connection.password", dbSettings.getPassword())
                .applySetting("hibernate.connection.driver_class", dbSettings.driverClassName())
                .applySetting("hibernate.dialect", dbSettings.dialect())
                .applySetting("show.sql", "false")
                .build();

        Metadata metadata = new MetadataSources()
                .addResource(ormMapping)
                .addPackage("specify_here_your_package_with_entities")
                .getMetadataBuilder(registry)
                .build();

        new SchemaUpdate((MetadataImplementor) metadata).execute(false,true);
    }
}

其中DB设置只是一个接口(你应该根据你的需要来实现它):

public interface DBSettings {

    int getPort();

    String getServer();

    String getSelectedDataBaseName();

    String getPassword();

    String getUserName();

    String dbmsType();

    String JDBConnectionURL();

    String driverClassName();

    String dialect();
}

拥有自己的 DBSettings 实现并在 Spring 上下文中构建 DBSettingsSwitcher,现在您只需调用 DBSettingsSwitcher.applySettings(dbSettingsIml),您的数据请求就会被路由到新的数据源。

【讨论】:

    猜你喜欢
    • 2018-07-27
    • 1970-01-01
    • 1970-01-01
    • 2015-09-17
    • 2020-12-24
    • 2018-10-03
    • 1970-01-01
    • 2020-10-30
    • 2017-10-11
    相关资源
    最近更新 更多