簡體   English   中英

如何在運行時添加新數據源 Spring Boot

[英]How to add new Datasource at runtime Spring Boot

我有這個,但我找不到任何可能的解決方案。 我找到的所有答案都是關於配置兩個或多個 Datasource 或 Multitenant Dabatase,但這不是我需要的。
我必須這樣做:

  1. 從 application.properties 配置第一個數據源。 這是主要的數據庫配置
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "primaryEntityManagerFactory",
        basePackages = "com.example.primary")
public class SmartConnectConfig {

    @Primary
    @Bean(name = "primaryDatasource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.password"));
        return dataSource;
    }

    @Primary
    @Bean(name = "primaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("primaryDatasource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.primary")
                .persistenceUnit("primary")
                .build();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
  1. 當用戶根據組織連接到系統中時,查詢並獲取保存在第一個數據庫中的所有屬性或組織數據庫,以獲取第二個數據源的配置(url、用戶名、密碼、driverClassName)。 非常重要,因為根據用戶登錄,我需要根據組織創建不同的數據源。
  2. 添加到 spring 以使用第二個數據源管理一些存儲庫
    注意:第一個和第二個存儲庫位於不同的 package 中,因為 Spring 可以掃描這些存儲庫,第二個 package 是“com.example.second”。

有人可以給一些建議。
謝謝

在 Spring Boot 中有大量資源解釋如何配置數據源,使用各種選項和各種方式來檢索連接的配置詳細信息(從外部文件、從 application.properties、硬編碼等)。 另一方面,如果您已經配置了一個數據源並且在運行時您想要更改此連接的詳細信息(可能是數據庫更改的主機名)而不重新啟動應用程序,則信息有點稀缺。

我所說的用例是允許用戶更改全部或部分數據庫連接詳細信息(例如主機名)的情況。 用戶有一個設置頁面,他可以在其中編輯這些詳細信息。 此設置保存在外部文件中,而不是在 application.properties 中。 在這種情況下,對於對該數據庫的后續請求,需要使用新參數。 Spring 的默認行為是將 Beans 創建為 Singleton,包括您配置的 DataSource,因此,如果您更改設置,則不會重新創建 bean(它仍將使用舊參數)。

在這里,我將介紹如何實現這一目標的眾多方法中的一種。

創建數據源

有多種方法可以創建數據源以在 Spring Boot 中使用,並且為此提供了大量資源。 在這里,我將展示如何創建一個簡單的基於 JdbcTemplate 的存儲庫,該存儲庫在單獨的 package 中配置(不會使用 application.properties 中定義的數據庫屬性)。

這是數據源的配置片段:

@Configuration
public class CustomDataSourceConfiguration {

    @Lazy
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public DataSource customDataSource() {
        DataSourceBuilder dsBuilder = DataSourceBuilder.create();
        dsBuilder.driverClassName(„oracle.jdbc.driver.OracleDriver“);
        CustomDatabaseSettings dbSettings = <….>//Here obtain the settings from whereever you need
        dsBuilder.url(dbSettings.jdbcUrl());
        dsBuilder.username(dbSettings.username());
        dsBuilder.password(dbSettings.password());
        return dsBuilder.build();
    }

    @Lazy
    @Qualifier(„customJdbcTemplate“)
    @Bean
    public JdbcTemplate customJdbcTemplate() {
        return new JdbcTemplate(customDataSource());
    }

}

在這里,使用注釋為配置 class 的 class,我定義了 DataSource 和 JdbcTemplate 所需的 Bean。 @Lazy 注釋在這種情況下很重要,因為我從 Java 代碼讀取了包含數據庫配置的外部文件,這發生在 Spring 容器初始化所有 bean 之后。 默認情況下,Beans 是 Eagerly 加載的,這意味着當 Spring 掃描到它們並遇到它們時,它將實例化它們。 Lazy 將等待初始化,直到對該 bean 發出第一個請求。 請參閱@Lazy 的文檔 – https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Lazy.html

bean 的 Scope 被設置為“Prototype”。 默認情況下,scope 設置為 Singleton,但正如我在文章開頭所說,我們需要一種方法來使用新的配置詳細信息重新初始化數據源,最簡單的方法是使用這個 scope。這個 scope 每次都會調用初始化請求 bean。 從代碼中可以看出,這非常適合我們的情況,因為再次調用初始化方法也將獲得新配置的數據庫連接詳細信息。 使用 Prototype 有一些注意事項。 有關范圍的更多信息可以在文檔中找到: https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html

這也是存儲庫的代碼:

@Lazy
@Repository
@ComponentScan(basePackages = „com.cli.jdbc.datasource“)
public class CustomRepository{

    @Qualifier(„customJdbcTemplate“)
    @Autowired
    private JdbcTemplate jdbcTemplate;

//Implement methods here

因此,我們通過讀取在運行時動態更改的自定義配置文件來配置數據源。 現在,當設置更改時,我們需要一種方法來重新初始化數據源。 從概念上講,這是通過簡單地從 Spring 上下文中檢索數據源 bean 來完成的。 因為我們使用了 Prototype scope,這將導致再次調用上面定義的方法中的邏輯,這反過來將對我們的數據庫進行最新更改。 在這一步之后,我們還需要從上下文中檢索 jdbcTemplate 並將這個新的 DataSource 設置給它。

代碼如下所示:

public void refreshCustomJdbc() {
        DataSource ds = (DataSource) getSpringContext().getBean(„customDataSource“);
        JdbcTemplate customJdbcTemplate = (JdbcTemplate) getSpringContext().getBean(„customJdbcTemplate“);
        customJdbcTemplate.setDataSource(ds);
    }

參考: https://blog.virtual7.de/dynamically-change-data-source-connection-details-at-runtime-in-spring-boot/

我建議您嘗試將數據源信息存儲到HttpSession

例:

@RestController
public class CategoryController {

  @PostMapping("init")
  public void createDatasource(HttpSession session, @RequestBody CategoryRequestDto request) {
    if (session != null && session.isNew()) {
        DBInfo dbInfo = request.getUserinfo().getDatabaseInfo();
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setUsername(dbInfo.getUsername());
        hikariDataSource.setPassword(dbInfo.getPassword());
        hikariDataSource.setJdbcUrl("jdbc:h2:mem:"+session.getId());
        hikariDataSource.setDriverClassName(Driver.class.getName());
        session.setAttribute("datasource", hikariDataSource);// store datasource for later use
    }
  }
}

如果需要配置兩個數據源,則只需使用不同的名稱來調用它,例如:

@Bean("dataSource")
public DataSource getDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(this.hProperties.getProperty("db.driverClass"));
        dataSource.setUrl(this.hProperties.getProperty("db.location"));
        dataSource.setUsername(this.hProperties.getProperty("db.username"));
        dataSource.setPassword(this.hProperties.getProperty("db.password"));
        return dataSource;
    }
    @Bean("dataSourceTwo")
    public DataSource getDataSourceLucca() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(this.hProperties.getProperty("db.lucca.driverClass"));
        dataSource.setUrl(this.hProperties.getProperty("db.lucca.location"));
        dataSource.setUsername(this.hProperties.getProperty("db.lucca.username"));
        dataSource.setPassword(this.hProperties.getProperty("db.lucca.password"));
        return dataSource;
    }


@Bean("sessionFactory")
public LocalSessionFactoryBean getSessionFactory() {
    loadProperties();
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(getDataSource());
    sessionFactory.setPackagesToScan(new String[] { "com.monty.goofy" });
    sessionFactory.setHibernateProperties(getHibernateProperties());
    return sessionFactory;
}
@Bean("sessionFactoryTwo")
public LocalSessionFactoryBean getSessionFactoryTwo() {
    loadProperties();
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(getDataSourceTwo());
    return sessionFactory;
}
@Bean("transactionManager")
public HibernateTransactionManager transactionManager() {
    HibernateTransactionManager txManager = new HibernateTransactionManager();
    SessionFactory sessionFactory = getSessionFactory().getObject(); 
    txManager.setSessionFactory(sessionFactory);
    return txManager;
}
@Bean("transactionManagerTwo")
public HibernateTransactionManager transactionManagerLucca() {
    HibernateTransactionManager txManager = new HibernateTransactionManager();
    SessionFactory sessionFactoryTwo = getSessionFactoryTwo().getObject(); 
    txManager.setSessionFactory(sessionFactoryTwo);
    return txManager;
}

這是同時使用它們的示例:

@Override
@Transactional("transactionManager")
public void saveOne(){

}
@Override
@Transactional("transactionManagerTwo")
public void saveTwo(){

}

(這是手動配置)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM