多租户:使用Spring Data JPA管理多个数据源

时间:2018-04-10 17:26:36

标签: spring-boot spring-data-jpa

我需要创建一个可以管理多个数据源的服务。 当应用程序首次运行应用程序时,这些数据源不一定存在,实际上端点将创建新数据库,我希望能够切换到它们并创建数据。

例如,让我们说我有3个数据库,A,B和C,然后我启动应用程序,我使用创建D的端点,然后我想使用D。

这可能吗?

我知道如果存在这些数据源,如何切换到其他数据源,但我现在看不到任何可以使我的请求成为可能的解决方案。 你有什么想法吗?

由于

1 个答案:

答案 0 :(得分:5)

要使用Spring Boot实现多租户,我们可以使用AbstractRoutingDataSource作为所有' 租户数据库的基本 DataSource 类。

我们必须覆盖一个抽象方法determineCurrentLookupKey。它告诉AbstractRoutingDataSource目前必须提供哪个租户数据源。由于它在多线程环境中工作,因此所选租户的信息应存储在ThreadLocal变量中。

AbstractRoutingDataSource将租户数据源的信息存储在其私有Map<Object, Object> targetDataSources中。此映射的关键是租户标识符(例如String类型)和值 - 租户数据源。要将我们的租户数据源放入此地图,我们必须使用其setter setTargetDataSources

如果没有&#39;默认&#39; AbstractRoutingDataSource将不起作用我们必须使用方法setDefaultTargetDataSource(Object defaultTargetDataSource)设置的数据源。

在我们设置租户数据源和默认数据源之后,我们必须调用方法afterPropertiesSet()来告诉AbstractRoutingDataSource更新其状态。

所以我们的MultiTenantManager&#39; class可以是这样的:

@Configuration
public class MultiTenantManager {

    private final ThreadLocal<String> currentTenant = new ThreadLocal<>();
    private final Map<Object, Object> tenantDataSources = new ConcurrentHashMap<>();
    private final DataSourceProperties properties;

    private AbstractRoutingDataSource multiTenantDataSource;

    public MultiTenantManager(DataSourceProperties properties) {
        this.properties = properties;
    }

    @Bean
    public DataSource dataSource() {
        multiTenantDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                return currentTenant.get();
            }
        };
        multiTenantDataSource.setTargetDataSources(tenantDataSources);
        multiTenantDataSource.setDefaultTargetDataSource(defaultDataSource());
        multiTenantDataSource.afterPropertiesSet();
        return multiTenantDataSource;
    }

    public void addTenant(String tenantId, String url, String username, String password) throws SQLException {

        DataSource dataSource = DataSourceBuilder.create()
                .driverClassName(properties.getDriverClassName())
                .url(url)
                .username(username)
                .password(password)
                .build();

        // Check that new connection is 'live'. If not - throw exception
        try(Connection c = dataSource.getConnection()) {
            tenantDataSources.put(tenantId, dataSource);
            multiTenantDataSource.afterPropertiesSet();
        }
    }

    public void setCurrentTenant(String tenantId) {
        currentTenant.set(tenantId);
    }

    private DriverManagerDataSource defaultDataSource() {
        DriverManagerDataSource defaultDataSource = new DriverManagerDataSource();
        defaultDataSource.setDriverClassName("org.h2.Driver");
        defaultDataSource.setUrl("jdbc:h2:mem:default");
        defaultDataSource.setUsername("default");
        defaultDataSource.setPassword("default");
        return defaultDataSource;
    }
}

简要说明:

  • tenantDataSources映射到我们放置到setTargetDataSources setter的本地租户数据源存储空间;

  • DataSourceProperties properties用于从&#39; application.properties&#39;的spring.datasource.driverClassName获取租户数据库的数据库驱动程序类名称。 (例如,org.postgresql.Driver);

  • 方法addTenant用于将新租户及其数据源添加到本地租户数据源存储中。 我们可以动态执行此操作 - 感谢方法afterPropertiesSet();

  • 方法setCurrentTenant(String tenantId)用于切换&#39;到给定租户的数据源。我们可以在处理使用数据库的请求时在REST控制器中使用此方法。该请求应包含&{39; tenantId&#39;,例如在X-TenantId标题中,我们可以检索并放入此方法;

  • defaultDataSource()使用内存H2数据库构建,以避免在工作SQL服务器上使用默认数据库。

注意:必须spring.jpa.hibernate.ddl-auto参数设置为none才能禁用数据库架构中的Hibernate make更改。您必须事先创建租户数据库的模式。

此课程的完整示例以及您可以在 repo 中找到的更多内容。