spring-data-jpa 多租户:使用Spring Data JPA管理多个数据源

gfttwv5a  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(280)

我需要创建一个可以管理多个数据源的服务。这些数据源不一定存在,当应用程序第一次运行应用程序时,实际上一个端点将创建新的数据库,我希望能够切换到它们并创建数据。
例如,假设我有3个数据库,A、B和C,然后我启动应用,使用创建D的端点,然后我想使用D。
这可能吗?
我知道如何切换到其他数据源,如果有的话,但我现在看不到任何解决方案,使我的请求成为可能。你有什么想法吗?
谢谢

ijnw1ujt

ijnw1ujt1#

要使用Sping Boot 实现多租户,我们可以使用AbstractRoutingDataSource作为所有“* 租户数据库 *”的基本DataSource类。
它有一个抽象的方法determineCurrentLookupKey,我们必须覆盖它。它告诉AbstractRoutingDataSource它必须提供哪个租户数据源来工作。因为它工作在多线程环境中,所以所选租户的信息应该存储在ThreadLocal变量中。
AbstractRoutingDataSource将租户数据源的信息存储在其专用Map<Object, Object> targetDataSources中。此Map的关键字是租户标识符(例如字符串类型)和值-租户数据源。要将租户数据源放入此Map,必须使用其setter setTargetDataSources
AbstractRoutingDataSource在没有“默认”数据源的情况下将无法工作,我们必须使用setDefaultTargetDataSource(Object defaultTargetDataSource)方法设置该数据源。
在设置租户数据源和默认数据源之后,我们必须调用方法afterPropertiesSet()来告诉AbstractRoutingDataSource更新其状态。
因此,我们的'MultiTenantManager'类可以是这样的:

@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;
    }
}

简要说明:

  • map tenantDataSources它是我们的本地租户数据源存储,我们将其放入setTargetDataSources setter;
  • DataSourceProperties properties用于从“application.properties”的spring.datasource.driverClassName中获取租户数据库的数据库驱动程序类名(例如org.postgresql.Driver);
  • 方法addTenant用于将新租户及其数据源添加到我们的本地租户数据源存储中。我们可以在运行中完成此操作-这要归功于方法afterPropertiesSet();
  • 方法setCurrentTenant(String tenantId)用于“切换”到给定租户的数据源。例如,我们可以在REST控制器中处理使用数据库的请求时使用此方法。该请求应包含“tenantId”,例如在X-TenantId标头中,我们可以检索该值并将其放入此方法;
  • defaultDataSource()是使用内存中的H2数据库构建的,以避免使用正在工作的SQL服务器上的默认数据库。

注意:您必须spring.jpa.hibernate.ddl-auto参数设置为none,以禁用Hibernate在数据库方案中进行更改。您必须事先创建租户数据库的方案。
在我的**repo**中可以找到这个类的完整示例和更多内容。
已更新
这个branch演示了一个使用专用数据库来存储租户DB属性而不是属性文件的示例(请参见下面@MarcoGustavo的问题)。

相关问题