在本教程中,我们将学习如何在Spring Boot应用程序中提供多租户。
什么是多租户?
多租户是一种架构,其中单个软件应用程序实例为多个客户提供服务。每个客户都称为租户。租户可能被赋予自定义应用程序某些部分的能力,例如用户界面(UI)的颜色或业务规则,但不能自定义应用程序的代码。
实现此架构的多个众所周知的策略,从高度隔离(如单租户)到共享一切。我们可以使用以下任何方法之一来实现多租户:
- 每个租户一个数据库:每个租户都有自己的数据库,并与其他租户隔离。
- 共享数据库,共享架构:所有租户共享一个数据库和表。每个表都有一个带有租户标识符的列,显示该行的所有者。
- 共享数据库,单独的架构:所有租户共享一个数据库,但具有自己的数据库模式和表。
多租户模型
如何实现多租户?
我们可以使用Spring Boot和Hibernate来实现多租户架构的所有三种方法。
我们将使用以下步骤来实现多租户Rest API:
- 理解请求流程
- 确定租户
- 连接到数据库
- REST层
- 结论
先决条件
对于本教程,我们将使用Spring Boot 1.5.9.RELEASE项目,并使用以下依赖项:
spring-boot-starter-data-jpa
postgresql
spring-boot-starter-web
spring-boot-starter-actuator
1. 理解请求流程
建立多租户通信的过程通常由以下三个步骤组成:
- 接受传入连接,并在必要时对用户进行身份验证。
- 拦截请求并确定用户发出请求的租户。
- 与租户的数据库或模式建立连接。
租户标识针对包含用户信息的默认模式执行。用户可以在外部服务上对自己进行身份验证,然后使用HTTP标头传递租户信息。
为了保持简单,我们不执行任何类型的身份验证。我们将使用自定义HTTP标头“X-TenantID”来识别租户。让我们从识别租户开始。
2. 确定租户
首先,我们需要一种确定哪个租户正在发出请求的方法。我们将使用Spring Interceptor拦截HTTP请求并从HTTP标头获取租户信息。然后,所选租户存储在ThreadLocal变量中,在请求完成后清除。拦截器为每个请求获取“X-TenantID”HTTP标头的值,并在TenantContext
类中设置当前租户。如果未提供标头,则会响应错误。
RequestInterceptor
拦截器在WebMvcConfigurer类中配置。
CustomWebMvcConfigurer
TenantContext类用于存储每个请求的租户标识符。我们使用了InheritableThreadLocal变量。这使得我们应用程序中从主线程创建的子线程可以使用父线程的tenantId。
TenantContext
确定租户后,我们需要建立数据库连接。
3. 连接到数据库
我们可以使用下面讨论的任何三种方法之一连接到数据库。
让我们从每个租户具有单独的数据库开始。
3.1 每个租户一个数据库
我们将使用一个表在public架构中存储每个租户的数据库配置。使用数据库表的优点是,如果添加了新租户,则只需在表中添加一个新行,其中包含与租户相关的数据库配置。
创建表
CREATE TABLE if not exists public.DATASOURCECONFIG (
id bigint PRIMARY KEY,
driverclassname VARCHAR(255),
url VARCHAR(255),
name VARCHAR(255),
username VARCHAR(255),
password VARCHAR(255),
initialize BOOLEAN
);INSERT INTO DATASOURCECONFIG VALUES (1, 'org.postgresql.Driver', 'jdbc:postgresql://localhost:5432/tenant1?ApplicationName=MultiTenant', 'tenant1', 'postgres', 'postgres', true);
INSERT INTO DATASOURCECONFIG VALUES (2, 'org.postgresql.Driver', 'jdbc:postgresql://localhost:5432/tenant2?ApplicationName=MultiTenant', 'tenant2', 'postgres', 'postgres', true);
DataSourceConfig的JPA实体
DataSource Configuration
DataSourceConfig的JPA存储库
public interface DataSourceConfigRepository extends JpaRepository<DataSourceConfig, Long> {
DataSourceConfig findByName(String name);
}
让我们编写一个类来存储每个租户的数据库连接详细信息。我们将在服务器启动期间使用@PostConstruct加载连接详细信息。
用于存储TenantDataSources的Map
Hibernate配置
以下是必须执行的三个与Hibernate相关的配置,以启用多租户:
- CurrentTenantIdentifierResolver:它告诉Hibernate哪个是当前配置的租户。它使用拦截器设置的先前_ThreadLocal_变量。
TenantSchemaResolver
- AbstractDataSourceBasedMultiTenantConnectionProviderImpl:Spring Boot为根据租户标识符在运行时确定数据源提供了
AbstractRoutingDataSource
。我们需要覆盖selectDataSource方法。
AbstractDataSourceBasedMultiTenantConnectionProviderImpl
- HibernateConfig类组合这些部分并配置LocalContainerEntityManagerFactoryBean。在LocalContainerEntityManagerFactoryBean中,我们将MultiTenancyStrategy设置为DATABASE并覆盖连接提供程序和Tenant Identifier Resolver。
使用MultiTenancyStrategy.DATABASE的HibernateConfig
3.2 共享数据库,共享架构
Hibernate正式支持两种不同的多租户机制:单独的数据库和单独的架构。第三个Hibernate多租户机制是租户鉴别器,它也存在,并且可用 - 但仍被一些人视为工作正在进行中。与每个租户都需要不同的数据库连接的单独数据库和单独架构方法不同,Hibernate的租户鉴别器模型将租户数据存储在单个数据库中,并使用简单的列值分隔记录。我们将在所有表中添加一个名为tenant_id的列。然后,我们可以使用标准的Spring、Hibernate和AspectJ机制在运行时填充tenant_id列。定义接口
我们首先定义一个名为TenantSupport的接口,它将帮助我们识别具有多租户能力的实体。
public interface TenantSupport {
void setTenantId(String tenantId);
}
JPA实体
现在让我们定义带有tenantId过滤器的JPA实体。
定义类
该类使用标准的JPA和Hibernate注释进行定义。值得注意的是,@FilterDef
和@Filter
注释将允许我们向为此实体生成的每个SQL查询注入租户判别子句。我们将使用AspectJ建议来设置过滤器值。
AspectJ建议
当执行CityService类的任何数据访问方法时,aroundExecution()
方法将在City实体上启用Hibernate过滤器。它使用TenantContext
中的租户值填充过滤器条件,以将查询结果限制为仅匹配的记录。
Hibernate配置
以下是与Hibernate相关的配置:
- EmptyInterceptor:最后,我们需要在创建或销毁数据库记录时添加租户判别值。为此,我们需要使用Hibernate拦截器,如下所示:
Hibernate拦截器
Hibernate拦截器还使用来自TenantContext的tenantId。
一旦我们在Public模式下创建带有tenant_id列的实体,我们就可以开始了。根据请求中存在的“X-TenantID”头的值,“tenant_id”列将相应地填充。
3) 共享数据库,单独的模式
让我们看看使用每个租户模式和一个连接池的多租户实现需要哪些配置更改。
Hibernate配置
以下是三个与Hibernate相关的配置:
- CurrentTenantIdentifierResolver:它告诉Hibernate当前配置的租户是哪个。它使用拦截器设置的前一个_ThreadLocal_变量。如果没有找到租户ID,则使用Public模式作为默认租户。
TenantSchemaResolver
- MultiTenantConnectionProvider:Hibernate需要提供连接到上下文。我们从数据源获取连接并将其模式设置为相关租户。
MultiTenantConnectionProvider
- HibernateConfig类将这些部分组合起来,并配置LocalContainerEntityManagerFactoryBean。在LocalContainerEntityManagerFactoryBean中,我们将MultiTenancyStrategy设置为SCHEMA,并更新连接提供程序和Tenant Identifier Resolver。
具有MultiTenancyStrategy.SCHEMA的HibernateConfig
REST层
现在让我们看看如何使用上述任何一种多租户模型创建样本REST应用程序。我们将使用City REST资源来演示多租户方法。示例Rest应用程序将包括REST资源、Spring拦截器以选择和设置租户标识符以及将拦截器与REST资源相关联的配置。我们将公开City资源作为Web服务。
RestController
CityController用于公开与我们的资源相关的API。
RestController
Service
CityService用于编写与我们的资源相关的所有业务逻辑。
Repository
CityRepository用于执行资源上的所有与数据库相关的操作。
验证工作流程
测试和验证所需的基本信息如下:
我们将测试共享数据库,单独的模式。我们需要执行下面提到的DDL语句。
**注意:**租户将传递的X-TenantID值应与模式名称匹配。我们要支持哪些租户,就需要创建相应的模式。
以下脚本将创建数据库模式test1和test2。它还将在上述模式中创建所需的资源表以供测试使用。
##### Schema Creation ############
create schema if not exists test1;
create schema if not exists test2;###### Resource Table ###########
create table test1.city(id bigint, name varchar(200));
create table test2.city(id bigint, name varchar(200));
CREATE SEQUENCE "test1".hibernate_sequence
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
CREATE SEQUENCE "test2".hibernate_sequence
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
模式和表创建完成后,我们需要调用rest API来存储和检索数据。
调用Rest API将数据存储为X-TenantID:test1
curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -H 'X-TenantID: test1' -d '{"name":"Mumbai"}'
调用Rest API将数据存储为X-TenantID:test2
curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -H 'X-TenantID: test2' -d '{"name":"Kolkata"}'
调用Rest API检索X-TenantID:test1的数据
curl -X GET http://localhost:8080/ -H 'Content-Type: application/json' -H 'X-TenantID: test1'
调用Rest API检索X-TenantID:test2的数据
curl -X GET http://localhost:8080/ -H 'Content-Type: application/json' -H 'X-TenantID: test2'
结论:
每个模型在隔离和资源共享之间存在权衡,这在Microsoft:Popular multi-tenant data models中有详细说明。
我个人选择Schema-per-Tenant方法,因为它提供了租户之间的数据隔离,并且易于实现。
无论采取哪种方法,我们都需要在执行代码之前外部创建数据库、模式和表。一旦启用多租户,就需要在外部执行DDL。
如果你想参考完整的代码,请访问https://github.com/sumanentc/multitenant.git
参考文献:
使用Hibernate和Spring构建多租户Java应用程序抱歉,你提供的信息太少了,我不知道你需要翻译哪篇技术文章。请提供具体的文章或内容,让我能够更好地为你服务。
译自:https://medium.com/swlh/multi-tenancy-implementation-using-spring-boot-hibernate-6a8e3ecb251a
评论(0)