Spring boot - доступ к нескольким источникам данных в рамках одного запроса в мультитенантном приложении - PullRequest
0 голосов
/ 16 марта 2019

Это приложение извлекает субдомен из 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 .

  1. Извлечение субдомена из домена ex: tenant_1
  2. разрешить имена MongoDB и Mysql db для этого tenantId
  3. Использование обоих БД в бизнес-логике

Не могли бы вы объяснить, как достичь этой цели при загрузке Spring.

1 Ответ

0 голосов
/ 23 марта 2019

Мне пришлось переопределить 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...