Spring Boot – 根据功能开关动态选择具有不同SQL风格的数据源

huangapple go评论72阅读模式
英文:

Spring Boot - dynamically choose datasource with different SQL flavours based on a feature toggle

问题

我有一些现有的Spring Boot应用程序,它们使用Crud或Jpa存储库。我正在努力将它们从SQL Server迁移到Postgres。我希望能够根据功能切换的值,在运行时动态地交换使用的数据源。我一直在研究AbstractRoutingDatasource。似乎查找键可以是由我的功能切换系统提供的值。这与通常情况不同,但感觉可以工作。但我仍然不确定如何在存储库中使用它,以及是否允许我交换生成的SQL的方言。

英文:

I have a number of existing Spring Boot applications that use either Crud or Jpa repositories. I'm working on migrating them from SQL Server to Postgres. I'd like to be able to dynamically swap the used datasource at runtime based on a value of a feature toggle. I've been looking at AbstractRoutingDatasource. It seems that the lookup key could be a value provided by my feature toggle system. It would be different from the usual case, but feels it would work. I'm still not sure though how would I use it with the repositories and if it allowed me to swap the dialect of the generated SQL

答案1

得分: 1

以下是您提供的代码的中文翻译:

首先,您需要提供AbstractRoutingDataSource的实现:

public class DialectSpecificSourceRouting extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return Features.currentDialect();
    }
}

在尝试查找特定查找键时,将考虑此类。Features只是一个静态类,用于访问配置的方言。您也可以以另一种方式实现它。

接下来,您需要构建数据源。为此,您需要一种配置类,在其中可以将每个方言设置为特定的数据源配置。以下是一个最简示例:

@Configuration
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "foo")
public class MultiDialectConfiguration {

    public Set<String> dialects;

    public void setDialects(Set<String> dialects) {
        this.dialects = dialects;
    }
    private Map<String, DataSource> dataSourceMap;

    public Map<String, DataSource> getDataSourceMap() {
        return dataSourceMap;
    }

    @PostConstruct
    public void postConstruct() {
        this.dataSourceMap = this.dialects.stream()
                .collect(Collectors.toMap(Function.identity(), this::createDialectSpecificDataSource));
    }

    private DataSource createDialectSpecificDataSource(String dialect) {

        HikariConfig dialectSpecificConfig = new HikariConfig();
        dialectSpecificConfig.setJdbcUrl("urlfromConfig");
        dialectSpecificConfig.setUsername("usernamefromConfig");
        dialectSpecificConfig.setPassword("passwordfromconfig");
        dialectSpecificConfig.setPoolName(String.format("%s-db-pool", dialect));
        dialectSpecificConfig.setDriverClassName("YOUR");
        dialectSpecificConfig.setMaximumPoolSize(10);
        dialectSpecificConfig.setLeakDetectionThreshold(10);

        return new HikariDataSource(dialectSpecificConfig);
    }
}

该类包含一个映射,其中方言作为键,特定的数据源作为值。当然,您需要提供配置属性,例如从application.properties文件使用自己的前缀。

最后一步,我们需要使用先前配置的数据源映射提供我们的DataSource Bean

@Configuration
@RequiredArgsConstructor
public class DialectSpecificDataSource {
    private final MultiDialectConfiguration multiDialectConfiguration;

    @Bean
    public DataSource dataSource() {
        final DialectSpecificSourceRouting dialectSpecificDataSourceRouting = new DialectSpecificSourceRouting();
        final Map<Object, Object> targetDataSources = multiDialectConfiguration.getDataSourceMap()
                .entrySet()
                .stream()
                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));

        dialectSpecificDataSourceRouting.setTargetDataSources(targetDataSources);
        return dialectSpecificDataSourceRouting;
    }
}

这应该是实现您的要求的一种方式。通过这种方式,还可以实现在后台使用不同数据库的多租户应用程序。

英文:

First you need to provide an implementation of AbstractRoutingDataSource

public class DialectSpecificSourceRouting extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return Features.currentDialect();
    }
}

This class will be taken into account when trying to lookup the specific lookup key. The Features is just a static class which give access to the configured dialect. You can also implement it another way.

Next you need to build your datasources. For this you need some kind of configuration class where you can set each dialect to a specific data source configuration. Following is a minimal example:

@Configuration
@RequiredArgsConstructor
@ConfigurationProperties(prefix = &quot;foo&quot;)
public class MultiDialectConfiguration {

    public Set&lt;String&gt; dialects;

    public void setDialects(Set&lt;String&gt; dialects) {
       this.dialects = dialects;
    }
    private Map&lt;String, DataSource&gt; dataSourceMap;

    public Map&lt;String, DataSource&gt; getDataSourceMap() {
        return dataSourceMap;
    }

    @PostConstruct
    public void postConstruct() {
        this.dataSourceMap = this.dialects.stream()
                .collect(Collectors.toMap(Function.identity(), this::createDialectSpecificDataSource);
    }

    private DataSource createDialectSpecificDataSource(String dialect) {

        HikariConfig dialectSpecificConfig = new HikariConfig();
        dialectSpecificConfig.setJdbcUrl(&quot;urlfromConfig&quot;);
        dialectSpecificConfig.setUsername(&quot;usernamefromConfig&quot;);
        dialectSpecificConfig.setPassword(&quot;passwordfromconfig&quot;);
        dialectSpecificConfig.setPoolName(String.format(&quot;%s-db-pool&quot;, dialect);
        dialectSpecificConfig.setDriverClassName(&quot;YOUR&quot;);
        dialectSpecificConfig.setMaximumPoolSize(10);
        dialectSpecificConfig.setLeakDetectionThreshold(10);


        return new HikariDataSource(dialectSpecificConfig);
    }


}

This class contains a map which holds the dialect as Key and the specific DataSource as value. Sure, you need to provide the configuration properties from e.g. the application.properties file using own prefixes.

Last step we need to provide our DataSource Bean using the previously configured DataSource map.

@Configuration
@RequiredArgsConstructor
public class DialectSpecificDataSource {
    private final MultiDialectConfiguration multiDialectConfiguration;

    @Bean
    public DataSource dataSource() {
        final DialectSpecificSourceRouting dialectSpecificDataSourceRouting = new DialectSpecificSourceRouting();
        final Map&lt;Object, Object&gt; targetDataSources = multiDialectConfiguration.getDataSourceMap()
                .entrySet()
                .stream()
                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));

        dialectSpecificDataSourceRouting.setTargetDataSources(targetDataSources);
        return dialectSpecificDataSourceRouting;
    }
}

That should be a way to implement your requirements.

This way round it is also possible to implement a multi tenancy application using different databases in the background.

huangapple
  • 本文由 发表于 2023年5月24日 23:39:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76325255.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定