繁体   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