java - Spring boot - 在多租户应用程序中的单个请求中访问多个数据源
问题描述
此应用程序从请求 url 中提取子域,并将其用作tenantId
选择要连接的数据源。
public class TenantDetectionFilter extends GenericFilterBean {
private final MultiTenantManager multiTenantManager;
public TenantDetectionFilter(MultiTenantManager multiTenantManager) {
this.multiTenantManager = multiTenantManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
mapSubDomainToDataSource(getSubDomainFromDomain(servletRequest.getServerName()));
} catch (Exception e) {
//sending error
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
private void mapSubDomainToDataSource(String subDomain) throws Exception {
multiTenantManager.setCurrentTenant(subDomain);
}
private String getSubDomainFromDomain(@NotNull String domain) {
//logic to extract the sub-domain
}
}
该类MultiTenantManager
使用提取的子域将应用程序映射到相关的 MySql 数据库。
@Configuration
public class MultiTenantManager {
public static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
private final Map<Object, Object> tenantDataSources = new ConcurrentHashMap<>();
private static final String DB_CONNECTOR_DRIVER = "com.mysql.cj.jdbc.Driver";
private AbstractRoutingDataSource multiTenantDataSource;
@Bean
public DataSource dataSource() {
multiTenantDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return currentTenant.get();
}
};
multiTenantDataSource.setTargetDataSources(tenantDataSources);
multiTenantDataSource.setDefaultTargetDataSource(defaultDataSource());
multiTenantDataSource.afterPropertiesSet();
populateTenantsProd();
return multiTenantDataSource;
}
public void addTenant(String tenantId, String url, String username, String password) throws SQLException {
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(DB_CONNECTOR_DRIVER)
.url(url)
.username(username)
.password(password)
.build();
try (Connection c = dataSource.getConnection()) {
tenantDataSources.put(tenantId, dataSource);
multiTenantDataSource.afterPropertiesSet();
}
}
public void setCurrentTenant(String tenantId) throws Exception {
currentTenant.set(tenantId);
}
private DriverManagerDataSource defaultDataSource() {
DriverManagerDataSource defaultDataSource = new DriverManagerDataSource();
defaultDataSource.setDriverClassName(DB_CONNECTOR_DRIVER);
defaultDataSource.setUrl("jdbc:mysql://localhost:3306/defaultDB");
defaultDataSource.setUsername("username");
defaultDataSource.setPassword("password)");
return defaultDataSource;
}
private void populateTenantsProd() {
try {
addTenant("tenantId_1", "jdbc:mysql://localhost:3306/mysql_db_1", "username", "password");
addTenant("tenantId_2", "jdbc:mysql://localhost:3306/mysql_db_2", "username", "password");
} catch (Exception e) {
throw new RuntimeException();
}
}
}
到目前为止,一切运作良好。但是在场景中添加了一个新要求,即也使用MongoDB数据库。因此,当收到请求时,逻辑代码应该能够使用选定的(使用tenantId
)Mysql数据库来处理它的业务逻辑,而且它应该能够使用选定的(使用tenantId
)MongoDB 数据库来保存一些元数据。
我将Spring Data Jpa与Hibernate一起使用。
- 从域ex中提取子域:tenant_1
- 为此解析 MongoDB 和 Mysql 数据库名称
tenantId
- 在业务逻辑中使用两个数据库
您能否解释一下如何在 Spring Boot 中实现这一目标。
解决方案
我必须重写getDb(String dbName)
才能完成这项工作。
public class MongoFactory extends SimpleMongoDbFactory
{
private static final String DB_PREFIX = "prefix_";
public MongoFactory(MongoClientURI uri)
{
super(uri);
}
public MongoFactory()
{
super(new MongoClientURI("mongodb://localhost/test"));
}
@Override
public MongoDatabase getDb(String dbName) throws DataAccessException
{
if(Objects.nonNull(currentTenant.get())){
return super.getDb(DB_PREFIX+currentTenant.get());
}
return super.getDb();
}
}
而在MultiTenantManager
,
@Bean(name = "mongoDbFactory")
public MongoDbFactory mongoDbFactory()throws Exception
{
return new MongoFactory();
}
@Bean
public MongoTemplate mongoTemplate(@Qualifier("mongoDbFactory") MongoDbFactory dbFactory) throws Exception
{
return new MongoTemplate(dbFactory);
}
推荐阅读
- reactjs - Nginx 背后的 React 应用开发
- java - 无法阻止 BufferReader 在控制台中接受输入
- docker - 带有 Elixir CircleCI 配置的纱线:找不到 package.json 文件
- c# - 如何在 .NET Core 中裁剪图像?
- python - “TypeError:join() 参数必须是 str 或 bytes,而不是 'NoneType'”,使用 Magpie+Tensorflow/Python3
- python - 两台电脑之间通过 ssh 传输文件
- c# - C# 列表集合在方法之外失去其价值
- arrays - 从 Select-String 中取出每一行的最后一个单词
- powerbi - 如何在 Power BI 自定义视觉对象中设置 Bing 地图?
- scala - 单列分隔字符串 rdd 到正确列的数据框