多租户:使用 Spring Data JPA 管理多个数据源 [英] Multi-tenancy: Managing multiple datasources with Spring Data JPA

查看:67
本文介绍了多租户:使用 Spring Data JPA 管理多个数据源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to create a service that can manage multiple datasources. These datasources do not necessarily exist when the app when first running the app, actually an endpoint will create new databases, and I would like to be able to switch to them and create data.

例如,假设我有 3 个数据库,A、B 和 C,然后我启动应用程序,我使用创建 D 的端点,然后我想使用 D.

For example, let's say that I have 3 databases, A, B and C, then I start the app, I use the endpoint that creates D, then I want to use D.

这可能吗?

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

I know how to switch to other datasources if those exist, but I can't see any solutions for now that would make my request possible. Have you got any ideas?

谢谢

推荐答案

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

To implement multi-tenancy with Spring Boot we can use AbstractRoutingDataSource as base DataSource class for all 'tenant databases'.

它有一个抽象方法 determineCurrentLookupKey 我们必须重写.它告诉 AbstractRoutingDataSource 它现在必须提供哪个租户数据源才能使用.因为它工作在多线程环境中,所以选择租户的信息应该存储在ThreadLocal变量中.

It has one abstract method determineCurrentLookupKey that we have to override. It tells the AbstractRoutingDataSource which of the tenant datasource it has to provide at the moment to work with. Because it works in the multi-threading environment, the information of the chosen tenant should be stored in ThreadLocal variable.

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

The AbstractRoutingDataSource stores the info of the tenant datasources in its private Map<Object, Object> targetDataSources. The key of this map is a tenant identifier (for example the String type) and the value - the tenant datasource. To put our tenant datasources to this map we have to use its setter setTargetDataSources.

AbstractRoutingDataSource 如果没有我们必须使用方法 setDefaultTargetDataSource(Object defaultTargetDataSource) 设置的默认"数据源,将无法工作.

The AbstractRoutingDataSource will not work without 'default' datasource which we have to set with method setDefaultTargetDataSource(Object defaultTargetDataSource).

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

After we set the tenant datasources and the default one, we have to invoke method afterPropertiesSet() to tell the AbstractRoutingDataSource to update its state.

所以我们的MultiTenantManager"类可以是这样的:

So our 'MultiTenantManager' class can be like this:

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

  • map tenantDataSources it's our local tenant datasource storage which we put to the setTargetDataSources setter;

DataSourceProperties properties 用于从'application.properties'(例如, org.postgresql.Driver);

DataSourceProperties properties is used to get Database Driver Class name of tenant database from the spring.datasource.driverClassName of the 'application.properties' (for example, org.postgresql.Driver);

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

method addTenant is used to add a new tenant and its datasource to our local tenant datasource storage. We can do this on the fly - thanks to the method afterPropertiesSet();

method setCurrentTenant(String tenantId) 用于切换"到给定租户的数据源.例如,我们可以在 REST 控制器中使用此方法来处理使用数据库的请求.请求应包含tenantId",例如在 X-TenantId 标头中,我们可以检索并放入此方法;

method setCurrentTenant(String tenantId) is used to 'switch' onto datasource of the given tenant. We can use this method, for example, in the REST controller when handling a request to work with database. The request should contain the 'tenantId', for example in the X-TenantId header, that we can retrieve and put to this method;

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

defaultDataSource() is build with in-memory H2 Database to avoid using the default database on the working SQL server.

注意:您必须spring.jpa.hibernate.ddl-auto参数设置为none以禁用Hibernate在数据库中进行更改架构.您必须事先创建租户数据库的架构.

Note: you must set spring.jpa.hibernate.ddl-auto parameter to none to disable the Hibernate make changes in the database schema. You have to create a schema of tenant databases beforehand.

您可以在我的 repo 中找到此类的完整示例以及更多内容.

A full example of this class and more you can find in my repo.

更新

这个分支演示了一个示例使用专用数据库来存储租户数据库属性而不是属性文件(请参阅下面@MarcoGustavo 的问题).

This branch demonstrates an example of using the dedicated database to store tenant DB properties instead of property files (see the question of @MarcoGustavo below).

这篇关于多租户:使用 Spring Data JPA 管理多个数据源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆