要使用 Spring Boot 实现多租户,我们可以使用抽象路由数据源 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSource.html作为基础数据源所有人的班级'租户数据库'.
它有一个抽象方法确定当前查找键 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSource.html#determineCurrentLookupKey--我们必须重写。它告诉AbstractRoutingDataSource
目前必须提供哪个租户数据源才能使用。由于它工作在多线程环境中,因此所选租户的信息应存储在ThreadLocal
多变的。
The AbstractRoutingDataSource
将租户数据源的信息存储在其私有中Map<Object, Object> targetDataSources
。这张地图的关键是租户标识符(例如 String 类型)和值 -租户数据源。要将我们的租户数据源放入此地图,我们必须使用其设置器setTargetDataSources
.
The 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
用于从租户数据库获取数据库驱动程序类名称spring.datasource.driverClassName
“application.properties”的(例如,org.postgresql.Driver
);
method addTenant
用于将新租户及其数据源添加到我们的本地租户数据源存储中。我们可以即时完成此操作- 感谢方法afterPropertiesSet()
;
method setCurrentTenant(String tenantId)
用于“切换”到给定租户的数据源。例如,我们可以在 REST 控制器中处理使用数据库的请求时使用此方法。请求应包含“tenantId”,例如在X-TenantId
标头,我们可以检索该标头并将其放入此方法;
defaultDataSource()
使用内存中的 H2 数据库构建,以避免在工作 SQL 服务器上使用默认数据库。
注:你must set spring.jpa.hibernate.ddl-auto
参数为none
要禁用 Hibernate,请更改数据库架构。您必须事先创建租户数据库的架构。
此类的完整示例以及更多内容可以在我的中找到repo https://github.com/Cepr0/sb-multitenant-db-demo.
UPDATED
This branch https://github.com/Cepr0/sb-multitenant-db-demo/tree/tenant-in-db演示了使用专用数据库来存储租户数据库属性而不是属性文件的示例(请参阅下面@MarcoGustavo 的问题)。