英文:
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 = "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);
}
}
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<Object, Object> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论