此应用程序从请求网址中提取子域,并将其用作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 数据库用于其业务逻辑,此外,逻辑代码还应该能够使用选定的(使用{{1 }})MongoDB数据库,用于保存一些元数据。
我将 Spring Data Jpa 与休眠结合使用。
tenantId
的MongoDB和Mysql数据库名称能否请您解释一下如何在Spring Boot中实现此目标。
答案 0 :(得分:0)
我必须重写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);
}